# umemo
## Goal
This is the first challenge out of three in the fullchain challenge series. Weare given a QEMU VM, into which we can log in using the `ctf` user, but insteadof a shell we get a memo app. The goal is to get a shell and read the firstflag.
The author has provided the source code for the memo app and for the subset ofthe driver that the app talks so.
## App
The app opens the device and `mmap()`s the first page. It splits the page into16 256-byte parts. The first part contains the pointers to the other 15 parts.The user can read and write into the other parts.
The user can also read and write into the device by specifying an offset and asize. The first page is excluded from this with the following check:
```if((offset = getint() + 0x1000) < 0x1000 || lseek(fd, offset, SEEK_SET) < 0){        puts("Out of range");```
If we are somehow able to circumvent this and access the first page anyway, weget the arbitrary read/write capability.
## Vulnerability
The maximum amount of data that the device can hold is `MEMOPAGE_SIZE_MAX`,which is 1G. If we start the access from the last byte, the offset will wraparound and start referencing the first page.
The module stores the data with page granularity. The functions `get_memo_ro()`and `get_memo_rw()` return the pointer to the page associated with a givenoffset. `chrdev_read()` and `chrdev_write()` loop over pages and perform therespective operation. Unlike `chrdev_seek()`, they don't have any boundschecks, so it's only natural to wonder what would happen if the loop starts inbounds, but goes out of bounds.
Unfortunately one cannot see this in the provided `char.c` file. One can,however, either reverse the module, or get the `memo.c` file from the nextkmemo challenge.
Ultimately the problem is that `__pgoff_to_memopage()` looks only at the lower30 bits of the offset and ignores the others, so `MEMOPAGE_SIZE_MAX` is treatedthe same way as 0.
## Exploitation plan
There are many ways to leverage the arbitrary read-write capability for gettingcode execution. The leaked part addresses point to the mmap area, which alsocontains libc. We don't know where the binary, the heap and the stack are.
I chose faking an `atexit()` descriptor. libc's `__exit_funcs` variable pointsto a `struct exit_function_list`:
```struct exit_function_list  {    struct exit_function_list *next;    size_t idx;    struct exit_function fns[32];  };```
For our purposes `next` needs to be `NULL` and `idx` needs to be 1 (the numberof elements in the following array). `struct exit_function` is defined asfollows:
```struct exit_function  {    /* `flavour' should be of type of the `enum' above but since we need       this element in an atomic operation we have to use `long int'.  */    long int flavor;    union      {        void (*at) (void);        struct          {            void (*fn) (int status, void *arg);            void *arg;          } on;        struct          {            void (*fn) (void *arg, int status);            void *arg;            void *dso_handle;          } cxa;      } func;  };```
There are 3 different function pointer types that one can call, distinguishedby the `flavor` value: without arguments (`ef_at == 3`), with `int, void *`arguments (`ef_on == 2`) and with `void *, int` arguments (`ef_cxa == 4`).
The easiest would be to use any of them with one_gadget. Unfortunately we haveno means to satisfy the one_gadget constraints in this challenge. Analternative is to use `ef_cxa` with `system()` and `"/bin/sh"`.
Using arbitrary write, one can craft an `exit_function_list` instance in libc's`.bss` and overwrite `__exit_funcs` with a pointer to it.
## TTY problems
We don't interface with the memo application's stdin/stdout directly. Betweenus sits the TTY layer, which does ugly things to the data we send. As long aswe deal with printable characters, everything is fine and dandy. But we need tosend binary data in order to fake the part pointers and the `atexit()`descriptor. And some of that data may be treated as terminal controlcharacters.
[`man 3 termios`](https://man7.org/linux/man-pages/man3/termios.3.html) is ourbest friend here. From that we learn that `0x7f` aka `0177` is a controlcharacter called `VERASE`, which is essentially a backspace. This one is veryimportant, because it occurs in all the `mmap()`ped addresses. This handicapsus severely: instead of directly crafting what we need, we need to do partialoverwrites on data that already contains `0x7f`. For example, the first partpointer already has it. Therefore, it's sufficient to overwrite only its lowest5 bytes.
Here comes the next problem: it appears that any data we send must end with anewline, or the TTY layer won't forward it to the app. Of course, we could trysending a lot and exhaust some internal buffer, which would force a flush, butwe need to send exactly 5 bytes. Fortunately for us, the manual points to the`VEOF` control character, aka `0x4`, aka `Ctrl-D`, which forces a flush. Thedownside is, of course, that we cannot use `0x4` in our payloads as well.
How many more characters are there that we can't use? I counted these:
```BAD_CHARS = b"\x03\x04\x0a\x11\x13\x7f"```
This means we have to restart the app until ASLR gives us an address withoutany of them. An easy fix, by the way, would be to use the `VLNEXT` aka `0x16`aka `026` control character, which escapes the next character after it.Unfortunately it's recognized only when `IEXTEN` is set, and it's not. This can be confirmedby getting the shell in the local setup and running:
```$ stty -a[...]isig icanon -iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprtechoctl echoke -flusho -extproc```
Back to `0x4`, it's `ef_cxa`, which we need for our exploitation. So we need tofind an existing writable memory region, which contains:
```04 .. .. .. .. .. .. .... .. .. .. .. .. .. .... .. .. .. .. 7f .. ..```
and fill the blanks with the `struct exit_function_list` contents. I ended upeyeballing the `.data` and `.bss` sections and found such a location relativelyquickly.
Finally, the initial `__exit_funcs` value is `&initial`, which is inside libc,so a partial overwrite works for it as well.
## Debugging
The challenge runs in a VM with quite a primitive userspace. The debugger isnot available and building one is a hassle. How to debug crashes and eyeballsection contents?
I used the [initramfs-wrap](https://github.com/mephi42/initramfs-wrap/) script,which adds a Debian root that contains GDB (and anything else one can wish for)and moves the original contents into a chroot. It allowed me to run thechallenge in one tmux tab and GDB in the other. It was also possible to runGDB on the host and connect it to the gdbserver in the guest, since the guestkernel was compiled with network support.
## Flag
`SECCON{k3rn3l_bug5_5p1ll_0v3r_1n70_u53r_pr0gr4m5}`
The code uses nested C functions (a GNU extension), which makes the stackexecutable. One can find the location of the stack by looking at the `environ`variable, which is located inside libc. This opens up an alternativeexploitation direction: write the shellcode to the stack.