Tags: reversing

Rating:

# About Time

## Description
> Let's see if you can figure out this password..
>
> Use the following command to connect to the server and interact with the binary!
>
> nc challenges.tamuctf.com 4321

Tha binary corresponding to the service is provided.

## Solution

Let's connect to the service.

![about time](../images/abouttime.png)

So we get the current time, an encrypted password, and we need to enter the password. We need to understand how the password is encrypted.

Let's reverse the code using [Ghidra](https://ghidra-sre.org/).

Ghidra doesn't recognize the main function, but there is an entry function calling FUN_001015c7, so we'll assume this is the main function. Its code is the following:

c
undefined8 FUN_001015c7(void)

{
uint uVar1;
int iVar2;
time_t tVar3;
size_t sVar4;
long in_FS_OFFSET;
int local_9c;
int local_98;
time_t local_90;
tm *local_88;
FILE *local_80;
code *local_78 [3];
char local_5d [5];
char local_58 [56];
long local_20;

local_20 = *(long *)(in_FS_OFFSET + 0x28);
signal(0xe,FUN_00101569);
alarm(5);
tVar3 = time((time_t *)0x0);
srand((uint)tVar3);
local_90 = time((time_t *)0x0);
local_88 = localtime(&local_90);
strftime(local_58,0x32,"%Y-%m-%dT%H:%M:%S%Z",local_88);
strftime(local_5d,5,"%M",local_88);
iVar2 = atoi(local_5d);
iVar2 = iVar2 % 6;
uVar1 = iVar2 + 2;
DAT_00104100 = uVar1;
local_80 = fopen("flag.txt","r");
if (local_80 == (FILE *)0x0) {
puts("Flag.txt not found. Exiting...");
}
else {
fgets(&DAT_00104080,0x32,local_80);
fclose(local_80);
local_9c = 0;
while( true ) {
sVar4 = strlen(&DAT_00104080);
if (sVar4 <= (ulong)(long)local_9c) break;
if ((&DAT_00104080)[local_9c] == '\n') {
(&DAT_00104080)[local_9c] = 0;
}
local_9c = local_9c + 1;
}
strncpy(&DAT_001040c0,&DAT_00104080,0x32);
local_78[0] = FUN_001012a9;
local_78[1] = FUN_001013dd;
local_78[2] = FUN_00101473;
(*local_78[(int)uVar1 % 3])(&DAT_001040c0,(ulong)uVar1,local_78[(int)uVar1 % 3]);
(*local_78[(iVar2 + 3) % 3])(&DAT_001040c0,(ulong)uVar1,local_78[(iVar2 + 3) % 3]);
(*local_78[(iVar2 + 4) % 3])(&DAT_001040c0,(ulong)uVar1,local_78[(iVar2 + 4) % 3]);
printf("%s> Encrypted password: %s\n",local_58,&DAT_001040c0);
fflush(stdout);
printf("Enter the password: ");
fflush(stdout);
fgets(&DAT_00104104,0x32,stdin);
local_98 = 0;
while( true ) {
sVar4 = strlen(&DAT_00104104);
if (sVar4 <= (ulong)(long)local_98) break;
if ((&DAT_00104104)[local_98] == '\n') {
(&DAT_00104104)[local_98] = 0;
}
local_98 = local_98 + 1;
}
(*local_78[(int)uVar1 % 3])(&DAT_00104104,(ulong)uVar1,local_78[(int)uVar1 % 3]);
(*local_78[(iVar2 + 3) % 3])(&DAT_00104104,(ulong)uVar1,local_78[(iVar2 + 3) % 3]);
(*local_78[(iVar2 + 4) % 3])(&DAT_00104104,(ulong)uVar1,local_78[(iVar2 + 4) % 3]);
FUN_001014fb(&DAT_001040c0,&DAT_00104104);
}
if (local_20 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return 0;
}


Let's see in detail what this program does.

c
signal(0xe,FUN_00101569);
alarm(5);


First it sets an alarm in 5 seconds, and this alarm triggers FUN_00101569. This function basically sets some data blocks to 0 with probability 1/2.

c
tVar3 = time((time_t *)0x0);
srand((uint)tVar3);
local_90 = time((time_t *)0x0);
local_88 = localtime(&local_90);
strftime(local_58,0x32,"%Y-%m-%dT%H:%M:%S%Z",local_88);
strftime(local_5d,5,"%M",local_88);
iVar2 = atoi(local_5d);
iVar2 = iVar2 % 6;
uVar1 = iVar2 + 2;
DAT_00104100 = uVar1;


So the pseudo random generator is initialized with current time, then time is called once more and its format is set in local_58. Then two variables are set:
- iVar2 is current minute mod 6
- DAT_00104100 = uVar1 is iVar2 + 2.

c
local_80 = fopen("flag.txt","r");
if (local_80 == (FILE *)0x0) {
puts("Flag.txt not found. Exiting...");
}
else {
fgets(&DAT_00104080,0x32,local_80);
fclose(local_80);


Read content of flag.txt and put it in DAT_00104080.

c
local_9c = 0;
while( true ) {
sVar4 = strlen(&DAT_00104080);
if (sVar4 <= (ulong)(long)local_9c) break;
if ((&DAT_00104080)[local_9c] == '\n') {
(&DAT_00104080)[local_9c] = 0;
}
local_9c = local_9c + 1;
}


Replace any \n in the flag by 0.

c
strncpy(&DAT_001040c0,&DAT_00104080,0x32);
local_78[0] = FUN_001012a9;
local_78[1] = FUN_001013dd;
local_78[2] = FUN_00101473;
(*local_78[(int)uVar1 % 3])(&DAT_001040c0,(ulong)uVar1,local_78[(int)uVar1 % 3]);
(*local_78[(iVar2 + 3) % 3])(&DAT_001040c0,(ulong)uVar1,local_78[(iVar2 + 3) % 3]);
(*local_78[(iVar2 + 4) % 3])(&DAT_001040c0,(ulong)uVar1,local_78[(iVar2 + 4) % 3]);
printf("%s> Encrypted password: %s\n",local_58,&DAT_001040c0);
fflush(stdout);


Now things get interesting. First, copy the flag in DAT_001040c0. Then, 3 functions are saved in local_78, which is an array of function pointers. Then those functions are called in an order defined by the current minute (using iVar2 and uVar1), with DAT_001040c0 and uVar1 as parameter. This is the encrypted password which is then displayed to the user.

c
fgets(&DAT_00104104,0x32,stdin);
local_98 = 0;
while( true ) {
sVar4 = strlen(&DAT_00104104);
if (sVar4 <= (ulong)(long)local_98) break;
if ((&DAT_00104104)[local_98] == '\n') {
(&DAT_00104104)[local_98] = 0;
}
local_98 = local_98 + 1;
}
(*local_78[(int)uVar1 % 3])(&DAT_00104104,(ulong)uVar1,local_78[(int)uVar1 % 3]);
(*local_78[(iVar2 + 3) % 3])(&DAT_00104104,(ulong)uVar1,local_78[(iVar2 + 3) % 3]);
(*local_78[(iVar2 + 4) % 3])(&DAT_00104104,(ulong)uVar1,local_78[(iVar2 + 4) % 3]);
FUN_001014fb(&DAT_001040c0,&DAT_00104104);


Rest of the code is similar as above: the user inputs some password, which is then transformed as the flag, and both get compared in FUN_001014fb (which just prints gigem{password} if the password is correct).

So we need to understand what the three functions in local_78 do. As the time used to compute the minute is given, we know the order those functions are called. We also know the uVar1 parameter.

Let's describe the 3 functions.

**Function 0**:

c
void FUN_001012a9(char *param_1,int param_2)
{
int iVar1;
size_t sVar2;
int local_1c;

local_1c = 0;
while( true ) {
sVar2 = strlen(param_1);
if (sVar2 <= (ulong)(long)local_1c) break;
if (('' < param_1[local_1c]) && (param_1[local_1c] < '{')) {
iVar1 = param_2 + (int)param_1[local_1c] + 0x54;
param_1[local_1c] = (char)iVar1 + (char)(iVar1 / 0x1a) * -0x1a + 'a';
}
if (('@' < param_1[local_1c]) && (param_1[local_1c] < '[')) {
iVar1 = param_2 + (int)param_1[local_1c] + -0x34;
param_1[local_1c] = (char)iVar1 + (char)(iVar1 / 0x1a) * -0x1a + 'A';
}
local_1c = local_1c + 3;
}
return;
}


For characters 0, 3, ... in the input string, it first checks wether it is a lowercase letter. If that is the case, it tranforms it using the following transformation:

c
y = minutes + input[i] + 0x54
input[i] = remainder(y, 0x1a) + 'a'.


where minutes is the second parameter given to the function (derived in main from current minute), input[i] is the current character, and remainder(y, 0x1a) returns the remainder of the euclidian division of y by 0x1a.

Then a second check happens: if input[i] is between @ and [, then another similar computation is performed:

c
y = minutes + input[i] - 0x34
input[i] = remainder(y, 0x1a) + 'A'.


So let's try to invert this.

First check: it checks if the input is a lowercase letter, and it outputs also a modified lowercase letter. We actually have input[i] = 'a' + (input[i] + cst) % 26 with cst a constant depending of minutes.

The same reasoning can be performed with the second check but with uppercase.

Therefore we can reverse the function:

python
def fun0(flag, minutes):
for i in range(0, len(flag), 3):
if flag[i] in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
y = ord(flag[i])
y = y + 0x34 - minutes
y %= 26
flag[i] = chr(ord('A') + y)
if flag[i] in 'abcdefghijklmnopqrstuvwxyz':
y = ord(flag[i])
y = y - 0x54 - minutes
y %= 26
flag[i] = chr(ord('a') + y)
return flag


**Function 1**:

c
void FUN_001013dd(char *param_1,int param_2)
{
size_t sVar1;
long in_FS_OFFSET;
int local_50;
char local_48 [56];
long local_10;

local_10 = *(long *)(in_FS_OFFSET + 0x28);
strncpy(local_48,param_1,0x32);
sVar1 = strlen(param_1);
local_50 = 0;
while (local_50 < (int)sVar1) {
param_1[local_50] = local_48[(param_2 + local_50) % (int)sVar1];
local_50 = local_50 + 1;
}
if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) {
/* WARNING: Subroutine does not return */
__stack_chk_fail();
}
return;
}


This function is easier to understand. The input is copied to local_48 then the operation performed is input[i] = local48[(minutes + i)%n]. This Python function reverses the function:

python
def fun1(flag, minutes):
return [flag[(i - minutes) % len(flag)] for i in range(len(flag))]


**Function 2* is also easier:

c
void FUN_00101473(char *param_1,char param_2)
{
size_t sVar1;
int local_10;

sVar1 = strlen(param_1);
local_10 = 0;
while (local_10 < (int)sVar1) {
if (('/' < param_1[local_10]) && (param_1[local_10] < ':')) {
param_1[local_10] = param_1[local_10] + param_2;
}
local_10 = local_10 + 1;
}
return;
}


For all charachter in the input, if it is a number, add minutes to it.

python
def fun2(flag, minutes):
for i in range(len(flag)):
if ord(flag[i]) - minutes > ord('/') and ord(flag[i]) - minutes < ord(':'):
flag[i] = chr(ord(flag[i]) - minutes)
return flag


We can then combine those functions to get the flag:

python
fun = [fun0, fun1, fun2]

if __name__ == "__main__":
minutes = int(sys.argv[1])
flag = list(sys.argv[2])
minutes = minutes % 6
mod_min = minutes + 2
flag = fun[(minutes + 4) % 3](flag, mod_min)
flag = fun[(minutes + 3) % 3](flag, mod_min)
flag = fun[(mod_min) % 3](flag, mod_min)
print(''.join(flag))


And launch this program with 2 arguments: the minute given by the server, and the encrypted password.

Flag: gigem{1tsAbOut7iMet0geTaw47CH}`

Original writeup (https://github.com/apoirrier/CTFs-writeups/blob/master/TAMUCTF2020/Reversing/About%20time.md).