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.
main
int __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
.
setup
void 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_passcode
int 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.
menu
int 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
- easy
int __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
- hard
unsigned __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