This weekend, i joined TheFewChosen CTF 2023 where i attempted to solve pwn and few other challenges over there However, didn’t able to perform good but hmmm taught me plenty of new things and made me feel i am noob hehe just kidding i am already enough :p
some of pwn challenges i didn’t solve by myself i solved some after CTF by taking help from other participants In discord and by referring writeups i’ll gonna write those all as this blog is a medium for me to referencing the problems in future
so here we have BOF in fegets function variable local_108 being over feeded so firt thing comes into mind is that we can ret2shellcode if binary has NX disabled lets check
okay we do have binary with rwx so now lets craft the payload and feed the program but where to return so that we can make shellcode execute? firstly i struggled to find the solution as i forgot about helper function
helper func
so as you can see we do have gadget like jump rsp that can help to jump in to the stack and execute the shellcode so final payload will be like this
here we have format string vulnerability in this code and as RELRO is partial we can overwrite the GOT with win function, lets check the offset using this script
provides to two options 1 and 2 for read and write sequentially
that takes index and content(number) to write
that being stored on variable local_208 corresponding to given index
so bug spotted in line number 38 as we can see that variable local_208’s address being printed using %x format specifier, it seems that we may get libc address that will help us to calculate the base address, lets find out the offset
defmain(): r = conn() r.recvuntil(b"read!\n") for i inrange(0,300): if i % 3 != 0: r.sendline(b"2") r.sendlineafter(b"index: ",str(i).encode()) print(r.recvline()) pause()
r.interactive() if __name__ == "__main__": main()
using that if condition thing ‘cause of restriction we have in get_index function i.e.
gadget&0x00000000ffffffff and gadget>>32 is used to send the address in two half as it takes only 4 bytes at a time so first half lower bytes and then rest higher bytes sequentially
{ int iVar1; time_t tVar2; int i; int j; int counter; setup(); tVar2 = time((time_t *)0x0); srand((uint)tVar2); for (i = 0; i < 10; i = i + 1) { iVar1 = rand(); *(int *)(v + (long)i * 4) = iVar1; } puts("Guess my numbers!"); for (j = 0; j < 10; j = j + 1) { __isoc99_scanf(&DAT_0010201e,input + (long)j * 4); } counter = 0; while( true ) { if (9 < counter) { win(); return0; } if (*(int *)(v + (long)counter * 4) != *(int *)(input + (long)counter * 4)) break; counter = counter + 1; } puts("You didn\'t make it :("); /* WARNING: Subroutine does not return */ exit(0); }
function workflow :
Loop1: generates 10 random integer values using the rand function and stores them in an array-like structure starting from the memory address v. The use of the time function to seed the random number generator ensures that each time the program is run, it produces a different sequence of random values.
Loop2: let user to guess numbers
Loop3: compare the user given number to random generated number one by one if each and every number that being generated randomly in first loop is equal to user guessed numbers that it will call the win function that contain spwan the shell
so all we have to do is somehow generate the exact numbers that being generated by program
we can replicate the process in python using cytpes library
io = process() #io = remote('challs.tfcctf.com',31094) libc.srand(libc.time(0)) nbs = [] for i inrange(10): nbs.append(libc.rand()) io.recvuntil(b'Guess my numbers!\n') for i in nbs: io.sendline(str(i)) io.interactive()
pwngate
this challenge is pretty fun to solve, lets disassemble the program and follow through each function
main func
1 2 3 4 5 6 7 8 9 10 11 12
undefined8 main(void)
{ puts("Your current timeline is horrible."); sleep(1); puts("In order to reach the Pwn;Gate you are ready to sacrifice everything...\n"); sleep(1); setup(); current_leap(); menu(); return0; }
{ undefined4 local_11; undefined local_d; int local_c; printf("Enter your name: "); fgets(timeline,0xf,stdin); while( true ) { while( true ) { while( true ) { while( true ) { puts("\nWhat are you going to do?"); puts("-------------------------"); puts("[1] Try to change the timeline"); puts("[2] Time leap"); puts("[3] Ensure your sanity"); puts("[4] Remember who you are"); puts("[5] Exit"); puts("-------------------------"); printf("Enter choice: "); local_11 = 0; local_d = 0; fgets((char *)&local_11,5,stdin); local_c = atoi((char *)&local_11); if (local_c != 1) break; divergence_meter(); } if (local_c != 2) break; timeleap(); } if (local_c != 3) break; if (leveling._0_4_ == 0) { puts("You are sane."); } else { sanity(); } } if (local_c == 5) break; if (local_c == 4) { if (leveling._4_4_ == 0) { puts("You didn\'t play the game yet! :("); } else { okabe(); } } else { puts("Try again.\n"); } } /* WARNING: Subroutine does not return */ exit(0); }
It’s just a menu of functions that allows you to navigate to each function within the program by selecting the corresponding number, lets see each function one by one
This function divergence_meter reads user input, processing characters according to conditions, and stores them in the timeline array.
here in this function we have BOF in the fgets function local_2f local variable suppose to hold 8 byte of data but more than that has permitted and content from local_2f variable being copied to timeline variable and if we see in ghidra timeline is global variable
that also being used in other function as well, lets follow timeleap and timeleap and current_leap
okay so basically timeline is structure and this function assigns the function pointer fate to the _24_8_ member of the timeline structure. and also sets the _0_4_ , _24_8_ and _4_4_ members of the leveling structure to 0.
hmm so here timeline._24_8_ is working as function pointer and A function pointer is a variable that can store the memory address of a function. In this case, the code is setting the function pointer timeline._24_8_ to point to the fate function. This allows you to call the fate function indirectly through the function pointer timeline._24_8_.
timeleap func
1 2 3 4 5 6 7 8
voidtimeleap(void)
{ puts("You\'ve pressed the button... be ready for the leap!"); sleep(0); (*timeline._24_8_)(); return; }
and that function pointer being called in this timeleap function that is fate function
{ int iVar1; undefined4 local_ed; undefined local_e9; char local_e8 [50]; char local_b6 [50]; char local_84 [50]; char local_52 [54]; int local_1c; int i; int local_14; int local_10; uint local_c; puts("\nI don\'t recognize you, answer some questions first."); sleep(1); local_c = 0; local_10 = 0; while( true ) { while( true ) { puts("------------------------------"); puts("[1] Answer my questions"); puts("[2] Show your answers"); puts("[3] See your score"); puts("[4] Return"); puts("------------------------------"); printf("Choose: "); local_ed = 0; local_e9 = 0; fgets((char *)&local_ed,5,stdin); local_1c = atoi((char *)&local_ed); if (local_1c != 1) break; for (local_14 = 0; local_14 < 2; local_14 = local_14 + 1) { memset(local_e8 + (long)local_14 * 0x32,0,0x32); } puts("What is written on the Lab Members badge?"); fgets(local_e8,0x32,stdin); iVar1 = strcmp(local_e8,"OSHMKUFA 2010"); if (iVar1 != 0) { local_c = local_c + 1; } puts("What is the name of Okabe\'s Laboratory?"); fgets(local_b6,0x32,stdin); iVar1 = strcmp(local_b6,"Future Gagdet Laboratory"); if (iVar1 != 0) { local_c = local_c + 1; } puts("What is Mayuri\'s favorite hobby?"); fgets(local_84,0x32,stdin); iVar1 = strcmp(local_84,"Cosplay"); if (iVar1 != 0) { local_c = local_c + 1; } puts("Is Ruka a boy or girl?"); fgets(local_52,0x32,stdin); iVar1 = strcmp(local_52,"It depends on the timeline"); if (iVar1 != 0) { local_c = local_c + 1; } leveling._4_4_ = 1; puts("I think l remember you a bit now..."); } if ((local_1c == 2) && (local_10 != 1)) break; if (local_1c == 3) { local_10 = 1; printf("Your score is: %d \n",(ulong)local_c); } else { if ((local_1c != 2) || (local_10 != 1)) { return; } puts("These are your answers: "); for (i = 0; i < 4; i = i + 1) { puts(local_e8 + (long)i * 0x32); } } } puts("You didn\'t even start the game..."); /* WARNING: Subroutine does not return */ exit(0); }
okay simple quiz game. Users answer questions, accumulating a score for incorrect responses. They can choose to answer, display answers, or check their score. Incorrect answers increase the score. The function handles input, checks answers, and provides feedback.
{ int iVar1; char local_28 [12]; undefined local_1c; puts("What\'s the password?"); fgets(local_28,20,stdin); local_1c = 0; iVar1 = strcmp(leveling + 8,local_28); if (iVar1 != 0) { puts("That\'s wrong."); /* WARNING: Subroutine does not return */ exit(0); } printf("Do you still remember who you are?: "); fgets(timeline,0x20,stdin); return; }
Prompts the user to enter a password using fgets and stores it in the local_28 character array check for password correction, if incorrect exit the program or else it will prompt the user to enter data that being stored on timeline variable
and remember it require leveling._4_4_ != 0 to being called
okkayy lets sum up the information we got :
we have BOF in function divergence_meter that allows us to to write out of bound
but things get start serious when we jump to while loop where the content of local variable local_2f being copied into timeline variable, it loop for 9 time and content of local_2f variable being copied to timeline one by one, as index of timeline start from 16 that is timeline[(long)local_1c + 16] and goes until 9 byte of write (if(8<local_1c)) we are able to write next one byte of timeline i.e. timeline[24] and as we have seen in curretn_leap function this particular member of this structure is function pointer of fate function i.e. timeline._24_8_ = fate; so we have OBO bug here
function timeleap call the function i.e. (*timeline._24_8_)();
yet to see how function sanity is working or if it gives any information that can help us to pwn the binary as it is only callable if leveling._0_4_ != 0 we have to figure it out
function okabe asking for password that we don’t know about so far and this func is only callable when leveling._4_4_ != 0 as previous function
so lets focus on sanity and okabe function for now how we can make it call.
okay as leveling._4_4_ is global we can follow or cross reference where else this particular variable being used in program by double clicking on it (in ghidra)
so its used in some other function also where one of them is print_current_password where we can see leveling._0_4_ = 1 so this function make that particular variable value 1, so somehow we have to call this function to call sanity one
lets follow the parent function function of this particular one following XREF
so print_current_password -> verify_number -> whereami -> new_fate we have to call new_fate one
and same for okabe
the function we have to to call is sanity , we have to call sanity anyway
we leveraged the OBO bug to overwrite the LSB of one byte of fate address new_fate that gives us password
and thus we are allowed to call sanity that leaks the address (see the exploit)
Exploitation methodology :
using OBO overwrite the fate to new_fate as the first byte and a half is not changed by aslr so we can hardcode the byte
call the timeleap function so that we can make new_fate call undirectly using function pointer
grab the password from new_fate
grab the leak from sanity and calculate the base
use the pass and overwrite the address of timeline._24_8_ with win i.e. first 24 byte garbage and rest 8 byte address of win as we are allowed to write 32 byte
as we have overwritten timeline._24_8_ we can call it using timeleap function
We are sinking! The nearest ship got our SOS call, but they replied in pure gobbledygook! Are ye savvy enough to decode the message, or will we be sleepin’ with the fish tonight All hands on deck!
Whiskey Hotel Four Tango Dash Alpha Romeo Three Dash Yankee Oscar Uniform Dash Sierra One November Kilo India November Golf Dash Four Bravo Zero Uniform Seven