I explained the process of the functions in the binary given by pwnable problem, Wanna Play a Game, in 0xL4ugh CTF.
Fortunately, the binary was not stripped, so analyzing is easy.
mainint __cdecl __noreturn main(int argc, const char **argv, const char **envp)
{
__int64 v3; // [rsp+0h] [rbp-10h]
__int64 v4; // [rsp+8h] [rbp-8h]
setup();
printf("[*] NickName> ");
if ( read(0, &username, 0x40uLL) == -1 )
{
perror("READ ERROR");
exit(-1);
}
while ( 1 )
{
menu();
func = read_int();
printf("[*] Guess>");
param = read_int();
((void (__fastcall *)(__int64))conv[func - 1])(param);
}
}
This function sets default configuration settings using the function setup, then it stores the string to username from user. After then, it performs the functions in menu based on the user input. At this time, the method to call the functions is implemented by accessing the element from function array (conv) by getting the index from user, not using conditional jump.
setupvoid setup()
{
unsigned int v0; // eax
setvbuf(stdin, 0LL, 2, 0LL);
setvbuf(_bss_start, 0LL, 2, 0LL);
setvbuf(stderr, 0LL, 2, 0LL);
signal(14, handler);
alarm(0x3Cu);
change_passcode();
v0 = time(0LL);
srand(v0);
}
To prevent the string stored in Bufferend I/O is printed after the Buffered I/O is full, the function setup initializes the buffer. Then, it sets the time restriction of the exploit by 0x3C second using SIGALRM(14). After, it sets the passcode generated from /dev/random by calling the function change_passcode. Finally, it sets the seed for pseudo-random as current time (srand(time(NULL))).
change_passcodeint change_passcode()
{
int fd; // [rsp+Ch] [rbp-4h]
fd = open("/dev/random", 0);
if ( fd < 0 )
{
perror("OPEN ERROR");
exit(-1);
}
if ( read(fd, &passcode, 8uLL) == -1 )
{
perror("READ ERROR");
exit(-1);
}
puts("[*] PASSCODE CHANGED!");
return close(fd);
}
This sets 8 byte random value to the passcode by opening /dev/random file. /dev/random file stores the random value generated by system noise, etc.
menuint menu()
{
puts("= = = = = CAN YOU BEAT ME! = = = = =");
puts("[1] Easy");
return puts("[2] Hard");
}
This prints the menu, which represents the difficulty of a passcode leak.
read_int__int64 read_int()
{
__int64 buf[6]; // [rsp+0h] [rbp-30h] BYREF
buf[5] = __readfsqword(0x28u);
memset(buf, 0, 32);
printf("> ");
if ( read(0, buf, 0x20uLL) == -1 )
{
perror("READ ERROR");
exit(-1);
}
return atol((const char *)buf);
}
Ignore buf[5] = __readfsqword(0x28u); because it is related to Stack Canary.
This function translates 32byte string from user input to integer, then returns it.
conv - easyint __fastcall easy(__int64 param)
{
if ( param == rand() )
return printf("[+] NICE GUESS!!\n[*] Current Score: %lu\n", score);
else
return puts("[-] WRONG GUESS :(");
}
This checks the random value generated from rand() is the same as the given value param. In addition, the score is the global variable.
conv - hardunsigned __int64 __fastcall hard(__int64 param)
{
int i; // [rsp+14h] [rbp-2Ch]
char path[8]; // [rsp+2Fh] [rbp-11h] BYREF
char v4; // [rsp+37h] [rbp-9h]
unsigned __int64 v5; // [rsp+38h] [rbp-8h]
v5 = __readfsqword(0x28u);
strcpy(path, "<qz}<`{");
v4 = 0;
for ( i = 0; i <= 6; ++i )
path[i] ^= 0x13u;
if ( param == passcode )
{
puts("[+] WINNNN!");
execve(path, 0LL, 0LL);
}
else
{
puts("[-] YOU ARE NOT WORTHY FOR A SHELL!");
}
change_passcode();
return v5 - __readfsqword(0x28u);
}
This checks the random value read from /dev/random is the same as the given value param. If they are the same, then execute shell, if not, this function changes the passcode by calling the function change_passcode.

[Fig 1] Check the final value of the path used as the parameter of function execve