KPMG Cyber Security Challenge 2016 writeups

bounty_hunter

i end up wasting hours of my time solving this challenge, just because we were trolled by the organizer. the system somehow did not accept the correct flag and one of the crew did actually said that the flag was wrong.

anyway the protocols statistic shows that the packet consist of some mail packets:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
~/Desktop/kpmg/chal_mel ⌚ 15:30:45
$ tshark -r bounty_hunter_9b9a64928393b52380bcef3a5f5b3206.pcap -z io,phs
1 0.000000 192.168.9.153 -> 192.168.9.130 TCP 66 49332 → 110 [SYN] Seq=0 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
2 0.016303 192.168.9.130 -> 192.168.9.153 TCP 66 110 → 49332 [SYN, ACK] Seq=0 Ack=1 Win=8192 Len=0 MSS=1460 WS=256 SACK_PERM=1
3 0.016442 192.168.9.153 -> 192.168.9.130 TCP 54 49332 → 110 [ACK] Seq=1 Ack=1 Win=65536 Len=0
4 0.018267 192.168.9.130 -> 192.168.9.153 POP 64 S: +OK POP3
5 0.018469 192.168.9.153 -> 192.168.9.130 POP 61 C: AUTH
...
===================================================================
Protocol Hierarchy Statistics
Filter:

eth frames:1159 bytes:633144
ip frames:1159 bytes:633144
tcp frames:778 bytes:364644
pop frames:165 bytes:89890
imf frames:4 bytes:262
smtp frames:272 bytes:230625
imf frames:6 bytes:354
ftp frames:36 bytes:2825
ftp-data frames:3 bytes:2876
http frames:6 bytes:2408
data-text-lines frames:3 bytes:1140
data frames:1 bytes:55
ssl frames:25 bytes:12417
tcp.segments frames:3 bytes:4393
ssl frames:3 bytes:4393
udp frames:365 bytes:266764
nbns frames:24 bytes:2208
http frames:1 bytes:175
dns frames:18 bytes:1670
llmnr frames:8 bytes:568
quic frames:314 bytes:262143
icmp frames:16 bytes:1736
===================================================================

i decided to go through the emails to get an idea of whats going on

mail

we can see that looping is asking for dancing’s public key. he then attach an pgp encrypted jpg file(Marauder’s Map.JPG.gpg) in the subsequent mail. you can extract the encrypted file using NetworkMiner or do just like I did, manually

encrypted

logically, looping had the file encrypted with dancing’s public key. therefore, we need to get dancing’s secret key in order to decrypt it. searching for frames which contains “.gpg”, i stumbled upon an FTP STOR request frame which seems to be uploading dancing-backup-key.gpg to an ftp server

stor

we can extract the key from the pcap by following the tcp stream of the FTP-DATA protocol just a few frame after the STOR request and save it as a raw file

secret key

after importing dancing’s secret key to the keyring, we can try to decrypt the encrypted file.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~/Desktop/kpmg/bounty_hunter ⌚ 21:33:35
$ gpg --import dancing-backup-key.gpg
gpg: key E7661C2F: secret key imported
gpg: key E7661C2F: "dancing <dancing@cyberchallenge.com>" not changed
gpg: Total number processed: 1
gpg: unchanged: 1
gpg: secret keys read: 1
gpg: secret keys imported: 1

~/Desktop/kpmg/bounty_hunter ⌚ 21:33:41
$ gpg map.jpg.gpg

You need a passphrase to unlock the secret key for
user: "dancing <dancing@cyberchallenge.com>"
2048-bit RSA key, ID D11E5146, created 2016-11-08 (main key ID E7661C2F)

Enter passphrase:
gpg: Interrupt caught ... exiting

unfortunately, the file is password protected. running strings on the pcap did unearth some passwords, but none of them worked

1
2
3
4
5
6
~/Desktop/kpmg/bounty_hunter ⌚ 21:35:26
$ strings bounty_hunter_9b9a64928393b52380bcef3a5f5b3206.pcap| grep pass
+OK Send your password
PASS hardpassword
331 Please specify the password.
PASS harderpassword

i ran through the pcap again in wireshark and remembered that SMTP AUTH request are made in base64. copying the pass from the SMTP packet and base64 decoded it did the trick. the encrypted file was decrypted successfully

1
2
3
4
5
6
7
8
9
10
11
12
13
~/Desktop/kpmg/bounty_hunter ⌚ 21:38:29
$ echo "ZWFzeXBhc3N3b3Jk" | base64 -d
easypassword

~/Desktop/kpmg/bounty_hunter ⌚ 21:41:45
$ gpg map.jpg.gpg

You need a passphrase to unlock the secret key for
user: "dancing <dancing@cyberchallenge.com>"
2048-bit RSA key, ID D11E5146, created 2016-11-08 (main key ID E7661C2F)

gpg: encrypted with 2048-bit RSA key, ID D11E5146, created 2016-11-08
"dancing <dancing@cyberchallenge.com>"

unfortunately nothing interesting was in the picture

empty map

going through the pcap again i was able to find another Marauder’s Map.JPG.gpg with different bytes on frame 592. decrypting it reveals us the flag

flag

chal_mel

this was supposed to be an easy one, too bad i had my attention on another challenge. we were given with a pcap file full of http packets

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
~/Desktop/kpmg/chal_mel ⌚ 15:56:45
$ tshark -r chal_mel.pcap -z io,phs
1 0.000000 192.168.0.126 -> 192.168.0.50 TCP 78 64697 → 80 [SYN] Seq=0 Win=65535 Len=0 MSS=1460 WS=32 TSval=225483831 TSecr=0 SACK_PERM=1
2 0.000038 192.168.0.50 -> 192.168.0.126 TCP 74 80 → 64697 [SYN, ACK] Seq=0 Ack=1 Win=28960 Len=0 MSS=1460 SACK_PERM=1 TSval=23023905 TSecr=225483831 WS=128
3 0.002017 192.168.0.126 -> 192.168.0.50 TCP 66 64697 → 80 [ACK] Seq=1 Ack=1 Win=131744 Len=0 TSval=225483833 TSecr=23023905
4 0.002029 192.168.0.126 -> 192.168.0.50 HTTP 261 GET /?/9j/4AAQSkZJRgABAQAASABIAAD/4QBYRXhpZgAATU0AKgAAAAgAAgESAAMAAAAB HTTP/1.1
...
===================================================================
Protocol Hierarchy Statistics
Filter:

eth frames:188116 bytes:142555699
ip frames:188116 bytes:142555699
tcp frames:188116 bytes:142555699
http frames:21681 bytes:64188708
data-text-lines frames:10841 bytes:61359194
urlencoded-form frames:1 bytes:393
image-gif frames:1 bytes:439
===================================================================

the first GET request path literally screams base64, and decoding it shows:

1
2
3
4
5
~/Desktop/kpmg/chal_mel ⌚ 15:57:12
$ tshark -r chal_mel.pcap -Y "frame.number == 4" | awk '{print substr($9, 3)}' | base64 -d | xxd
00000000: ffd8 ffe0 0010 4a46 4946 0001 0100 0048 ......JFIF.....H
00000010: 0048 0000 ffe1 0058 4578 6966 0000 4d4d .H.....XExif..MM
00000020: 002a 0000 0008 0002 0112 0003 0000 0001 .*..............

that is obviously a jpg header. let’s try to decode all the GET request and redirect the stream to a jpg file

1
2
~/Desktop/kpmg/chal_mel ⌚ 15:57:59
$ tshark -r chal_mel.pcap -Y "http.request.method == GET" | awk '{print substr($9, 3)}' | base64 -d > flag.jpg

zooming in the decoded picture reveals us the flag

flag

cipher_text

we were given two files, a 64-bit elf binary and a text file containing some cipher. running strings on the binary give us a number of string references to “MEIPASS” which is a dead giveaway for PyInstaller

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~/Desktop/cipher_text ⌚ 15:20:41
$ strings cipher2 | head
/lib64/ld-linux-x86-64.so.2
/wIP
"!!2
libdl.so.2
...
Cannot open self %s or archive %s
_MEIPASS2
/proc/self/exe
...
pyi-
out of memory
_MEIPASS

let’s try to recover pyc file from the binary using pyi-archive_viewer which came bundled with PyInstaller. read more about it HERE

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
~/Desktop/cipher_text ⌚ 15:23:21
$ pyi-archive_viewer cipher2
pos, length, uncompressed, iscompressed, type, name
[(0, 172, 237, 1, 'm', u'struct'),
(172, 1141, 2543, 1, 'm', u'pyimod01_os_path'),
(1313, 3918, 10567, 1, 'm', u'pyimod02_archive'),
(5231, 6008, 18151, 1, 'm', u'pyimod03_importers'),
(11239, 1543, 4347, 1, 's', u'pyiboot01_bootstrap'),
(12782, 899, 2019, 1, 's', u'cipher2'),
(13681, 642535, 642535, 0, 'z', u'out00-PYZ.pyz')]
? x cipher2
to filename? cipher2.pyc
? q

~/Desktop/cipher_text ⌚ 15:24:21
$ ls -la
total 688
drwxrwxr-x 2 mkhdznfq mkhdznfq 4096 Nov 28 15:31 .
drwxr-xr-x 6 mkhdznfq mkhdznfq 4096 Nov 28 15:16 ..
-rwxr-xr-x 1 mkhdznfq mkhdznfq 687984 Nov 20 21:46 cipher2
-rw-rw-r-- 1 mkhdznfq mkhdznfq 2019 Nov 28 15:31 cipher2.pyc
-rw-rw-r-- 1 mkhdznfq mkhdznfq 145 Nov 20 21:46 cipher_text.txt

now we can try to retrieve original python script from the pyc file using uncompyle6

1
2
3
4
5
6
7
8
9
10
11
~/Desktop/cipher_text ⌚ 15:36:39
$ uncompyle6 cipher2.pyc -o cipher2.py
Traceback (most recent call last):
File "/usr/local/bin/uncompyle6", line 9, in <module>
load_entry_point('uncompyle6==2.9.6', 'console_scripts', 'uncompyle6')()
File "build/bdist.linux-x86_64/egg/uncompyle6/bin/uncompile.py", line 164, in main_bin
File "build/bdist.linux-x86_64/egg/uncompyle6/main.py", line 146, in main
File "build/bdist.linux-x86_64/egg/uncompyle6/main.py", line 62, in uncompyle_file
File "/usr/local/lib/python2.7/dist-packages/xdis/load.py", line 103, in load_module
(ord(magic[0])+256*ord(magic[1]), filename))
ImportError: Unknown magic number 99 in cipher2.pyc

uh-oh, looks like it cannot find the magic number in the header. this can be fixed by compiling our own pyc file and append the missing bytes to cipher2.pyc

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
~/Desktop/cipher_text ⌚ 15:40:30
$ touch dummy.py

~/Desktop/cipher_text ⌚ 15:40:37
$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import py_compile
>>> py_compile.compile("dummy.py")
>>>

~/Desktop/cipher_text ⌚ 15:41:07
$ (head -c 8 dummy.pyc && cat cipher2.pyc) > cipher2_fix.pyc

~/Desktop/cipher_text ⌚ 15:41:38
$ uncompyle6 -o ./ cipher2_fix.pyc
Successfully decompiled file

~/Desktop/cipher_text ⌚ 15:43:18
$ ls -la
total 700
drwxrwxr-x 2 mkhdznfq mkhdznfq 4096 Nov 28 15:46 .
drwxr-xr-x 6 mkhdznfq mkhdznfq 4096 Nov 28 15:16 ..
-rwxr-xr-x 1 mkhdznfq mkhdznfq 687984 Nov 20 21:46 cipher2
-rw-rw-r-- 1 mkhdznfq mkhdznfq 2034 Nov 28 15:46 cipher2_fix.py
-rw-rw-r-- 1 mkhdznfq mkhdznfq 2027 Nov 28 15:41 cipher2_fix.pyc
-rw-rw-r-- 1 mkhdznfq mkhdznfq 2019 Nov 28 15:31 cipher2.pyc
-rw-rw-r-- 1 mkhdznfq mkhdznfq 145 Nov 20 21:46 cipher_text.txt
-rw-rw-r-- 1 mkhdznfq mkhdznfq 0 Nov 28 15:40 dummy.py
-rw-rw-r-- 1 mkhdznfq mkhdznfq 95 Nov 28 15:41 dummy.pyc

let’s open cipher2_fix.py in a text editor and analyze the source code:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
# uncompyle6 version 2.9.6
# Python bytecode 2.7 (62211)
# Decompiled from: Python 2.7.12 (default, Nov 19 2016, 06:48:10)
# [GCC 5.4.0 20160609]
# Embedded file name: cipher2.py
# Compiled at: 2016-11-28 15:40:37
import sys
cipher_dict = {}
cipher_dict['1'] = '`4rM'
cipher_dict['0'] = '[0iR'
cipher_dict['3'] = '=8lK'
cipher_dict['2'] = '{0bE'
cipher_dict['5'] = '{4dW'
cipher_dict['4'] = '/7xK'
cipher_dict['7'] = '<3qU'
cipher_dict['6'] = '?9pB'
cipher_dict['9'] = '\x06vR'
cipher_dict['8'] = '$5wD'
cipher_dict['A'] = 'q[c3'
cipher_dict['C'] = 'a*v0'
cipher_dict['B'] = 'q<u1'
cipher_dict['E'] = 'm?n3'
cipher_dict['D'] = 'w%r0'
cipher_dict['G'] = 'o&h1'
cipher_dict['F'] = 'o!s8'
cipher_dict['I'] = 'i`d1'
cipher_dict['H'] = 'm,b9'
cipher_dict['K'] = 'w_f0'
cipher_dict['J'] = 'u^g1'
cipher_dict['M'] = "z'y6"
cipher_dict['L'] = 'i_g3'
cipher_dict['O'] = 'f(v9'
cipher_dict['N'] = 'l,o8'
cipher_dict['Q'] = 'b]w4'
cipher_dict['P'] = 'n@e4'
cipher_dict['S'] = 'b@i3'
cipher_dict['R'] = 'e~q7'
cipher_dict['U'] = "w'l2"
cipher_dict['T'] = 'r>j6'
cipher_dict['W'] = 't/k7'
cipher_dict['V'] = 't~b5'
cipher_dict['Y'] = 'r,b3'
cipher_dict['X'] = 'v/v9'
cipher_dict['Z'] = 'a#s7'
cipher_dict['_'] = '8$dY'
cipher_dict['a'] = 'B8g`'
cipher_dict['c'] = 'D1k~'
cipher_dict['b'] = 'L7o['
cipher_dict['e'] = 'J4d{'
cipher_dict['d'] = 'M3r\\'
cipher_dict['g'] = 'E9o~'
cipher_dict['f'] = 'K9m_'
cipher_dict['i'] = 'I7a\\'
cipher_dict['h'] = 'S6m`'
cipher_dict['k'] = 'H4q}'
cipher_dict['j'] = 'P6q}'
cipher_dict['m'] = 'T3k@'
cipher_dict['l'] = 'T8r)'
cipher_dict['o'] = 'M2s}'
cipher_dict['n'] = 'M5h{'
cipher_dict['q'] = 'J8k/'
cipher_dict['p'] = 'K4j~'
cipher_dict['s'] = 'X8o}'
cipher_dict['r'] = 'B7r>'
cipher_dict['u'] = 'X9u^'
cipher_dict['t'] = 'Y5u|'
cipher_dict['w'] = 'B4b%'
cipher_dict['v'] = 'H2n,'
cipher_dict['y'] = 'U6p.'
cipher_dict['x'] = 'G6i|'
cipher_dict['{'] = 'U-a9'
cipher_dict['z'] = 'D2e('
cipher_dict['}'] = '^Zr2'
if len(sys.argv) > 1:
l = list(sys.argv[1])
print cipher_dict[l[0]]
else:
sys.exit(1)

the script just take the first character of the argument passed and return a string based on the dictionary. this looks simple to reverse

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

cipher_dict = {}
cipher_dict['1'] = '`4rM'
cipher_dict['0'] = '[0iR'
cipher_dict['3'] = '=8lK'
cipher_dict['2'] = '{0bE'
cipher_dict['5'] = '{4dW'
cipher_dict['4'] = '/7xK'
cipher_dict['7'] = '<3qU'
cipher_dict['6'] = '?9pB'
cipher_dict['9'] = '\x06vR'
cipher_dict['8'] = '$5wD'
cipher_dict['A'] = 'q[c3'
cipher_dict['C'] = 'a*v0'
cipher_dict['B'] = 'q<u1'
cipher_dict['E'] = 'm?n3'
cipher_dict['D'] = 'w%r0'
cipher_dict['G'] = 'o&h1'
cipher_dict['F'] = 'o!s8'
cipher_dict['I'] = 'i`d1'
cipher_dict['H'] = 'm,b9'
cipher_dict['K'] = 'w_f0'
cipher_dict['J'] = 'u^g1'
cipher_dict['M'] = "z'y6"
cipher_dict['L'] = 'i_g3'
cipher_dict['O'] = 'f(v9'
cipher_dict['N'] = 'l,o8'
cipher_dict['Q'] = 'b]w4'
cipher_dict['P'] = 'n@e4'
cipher_dict['S'] = 'b@i3'
cipher_dict['R'] = 'e~q7'
cipher_dict['U'] = "w'l2"
cipher_dict['T'] = 'r>j6'
cipher_dict['W'] = 't/k7'
cipher_dict['V'] = 't~b5'
cipher_dict['Y'] = 'r,b3'
cipher_dict['X'] = 'v/v9'
cipher_dict['Z'] = 'a#s7'
cipher_dict['_'] = '8$dY'
cipher_dict['a'] = 'B8g`'
cipher_dict['c'] = 'D1k~'
cipher_dict['b'] = 'L7o['
cipher_dict['e'] = 'J4d{'
cipher_dict['d'] = 'M3r\\'
cipher_dict['g'] = 'E9o~'
cipher_dict['f'] = 'K9m_'
cipher_dict['i'] = 'I7a\\'
cipher_dict['h'] = 'S6m`'
cipher_dict['k'] = 'H4q}'
cipher_dict['j'] = 'P6q}'
cipher_dict['m'] = 'T3k@'
cipher_dict['l'] = 'T8r)'
cipher_dict['o'] = 'M2s}'
cipher_dict['n'] = 'M5h{'
cipher_dict['q'] = 'J8k/'
cipher_dict['p'] = 'K4j~'
cipher_dict['s'] = 'X8o}'
cipher_dict['r'] = 'B7r>'
cipher_dict['u'] = 'X9u^'
cipher_dict['t'] = 'Y5u|'
cipher_dict['w'] = 'B4b%'
cipher_dict['v'] = 'H2n,'
cipher_dict['y'] = 'U6p.'
cipher_dict['x'] = 'G6i|'
cipher_dict['{'] = 'U-a9'
cipher_dict['z'] = 'D2e('
cipher_dict['}'] = '^Zr2'

cipher_text = "w_f0n@e4z'y6o&h1U-a9`4rMq[c3T3k@B8g`T8r)=8lK/7xKK9m_[0iRM5h{<3qUS6m`=8lKB4b%`4rMM5h{M3r\B4b%/7xK<3qUa*v0m,b9S6m`[0iRt/k7I7a\{4dWM2s}/7xKB7r>^Zr2"
cipher_array = [cipher_text[i:i + 4] for i in range(0, len(cipher_text), 4)] # split cipher_text by 4
cipher_dict_rev = {v: k for k, v in cipher_dict.iteritems()} # reverse cipher_dict value and key

map(lambda x: sys.stdout.write(cipher_dict_rev[x]), cipher_array)

running the above script will generate us the flag

1
2
3
~/Desktop/kpmg/cipher_text2 ⌚ 15:54:43
$ python flag.py
KPMG{1Amal34f0n7h3w1ndw47CHh0Wi5o4r}

serious_traffic

we were distributed a pcap file (again!) and upon inspecting the protocol statistics, we can see it’s populated mostly by http traffics

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
~/Desktop/kpmg/serious_traffic ⌚ 16:15:58
$ tshark -r suspicious_traffic.pcap -z io,phs
1 0.000000 192.168.56.103 -> 192.168.56.101 TCP 64 1090 → 8080 [SYN] Seq=0 Win=64240 Len=0 MSS=1460 SACK_PERM=1
2 0.000016 192.168.56.101 -> 192.168.56.103 TCP 64 8080 → 1090 [SYN, ACK] Seq=0 Ack=1 Win=29200 Len=0 MSS=1460 SACK_PERM=1
3 0.000349 192.168.56.103 -> 192.168.56.101 TCP 62 1090 → 8080 [ACK] Seq=1 Ack=1 Win=64240 Len=0
...
===================================================================
Protocol Hierarchy Statistics
Filter:

sll frames:3964 bytes:3144264
ip frames:3964 bytes:3144264
tcp frames:3964 bytes:3144264
vssmonitoring frames:1447 bytes:89714
http frames:1856 bytes:510391
media frames:78 bytes:56538
tcp.segments frames:2 bytes:46832
===================================================================

going through the http traffics we can conclude that the packets are captured conversations of metasploit’s meterpreter. i decided to focus on the victim packets (192.168.56.103) and inspect the data passed to the attacker (192.168.56.101). i also filter out the broadcasting packet (contains RECV) which the victim send to the attacker

wireshark

if you look closely at each packet listed, you can see that in each post data is accompanied by the original command from the attacker after 16 bytes. therefore, we can just skim through the packets and only check for interesting commands. some of them are:

  • stdapi_ui_get_keys (dump keylogged buffer)
  • mimikatz_custom_command (mimikatz!)

to dump the keylogged buffer, i’ve tweak metasploit code in order to make it work with extracted hextream:

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

VirtualKeyCodes = {
1 => %W{ LClick },
2 => %W{ RClick },
3 => %W{ Cancel },
4 => %W{ MClick },
8 => %W{ Back },
9 => %W{ Tab },
10 => %W{ Newline },
12 => %W{ Clear },
13 => %W{ Return },
16 => %W{ Shift },
17 => %W{ Ctrl },
18 => %W{ Alt },
19 => %W{ Pause },
20 => %W{ CapsLock },
27 => %W{ Esc },
32 => %W{ Space },
33 => %W{ Prior },
34 => %W{ Next },
35 => %W{ End },
36 => %W{ Home },
37 => %W{ Left },
38 => %W{ Up },
39 => %W{ Right },
40 => %W{ Down },
41 => %W{ Select },
42 => %W{ Print },
43 => %W{ Execute },
44 => %W{ Snapshot },
45 => %W{ Insert },
46 => %W{ Delete },
47 => %W{ Help },
48 => %W{ 0 )},
49 => %W{ 1 !},
50 => %W{ 2 @},
51 => %W{ 3 #},
52 => %W{ 4 $},
53 => %W{ 5 %},
54 => %W{ 6 ^},
55 => %W{ 7 &},
56 => %W{ 8 *},
57 => %W{ 9 (},
65 => %W{ a A},
66 => %W{ b B},
67 => %W{ c C},
68 => %W{ d D},
69 => %W{ e E},
70 => %W{ f F},
71 => %W{ g G},
72 => %W{ h H},
73 => %W{ i I},
74 => %W{ j J},
75 => %W{ k K},
76 => %W{ l L},
77 => %W{ m M},
78 => %W{ n N},
79 => %W{ o O},
80 => %W{ p P},
81 => %W{ q Q},
82 => %W{ r R},
83 => %W{ s S},
84 => %W{ t T},
85 => %W{ u U},
86 => %W{ v V},
87 => %W{ w W},
88 => %W{ x X},
89 => %W{ y Y},
90 => %W{ z Z},
91 => %W{ LWin },
92 => %W{ RWin },
93 => %W{ Apps },
95 => %W{ Sleep },
96 => %W{ N0 },
97 => %W{ N1 },
98 => %W{ N2 },
99 => %W{ N3 },
100 => %W{ N4 },
101 => %W{ N5 },
102 => %W{ N6 },
103 => %W{ N7 },
104 => %W{ N8 },
105 => %W{ N9 },
106 => %W{ Multiply },
107 => %W{ Add },
108 => %W{ Separator },
109 => %W{ Subtract },
110 => %W{ Decimal },
111 => %W{ Divide },
112 => %W{ F1 },
113 => %W{ F2 },
114 => %W{ F3 },
115 => %W{ F4 },
116 => %W{ F5 },
117 => %W{ F6 },
118 => %W{ F7 },
119 => %W{ F8 },
120 => %W{ F9 },
121 => %W{ F10 },
122 => %W{ F11 },
123 => %W{ F12 },
124 => %W{ F13 },
125 => %W{ F14 },
126 => %W{ F15 },
127 => %W{ F16 },
128 => %W{ F17 },
129 => %W{ F18 },
130 => %W{ F19 },
131 => %W{ F20 },
132 => %W{ F21 },
133 => %W{ F22 },
134 => %W{ F23 },
135 => %W{ F24 },
144 => %W{ NumLock },
145 => %W{ Scroll },
160 => %W{ LShift },
161 => %W{ RShift },
162 => %W{ LCtrl },
163 => %W{ RCtrl },
164 => %W{ LMenu },
165 => %W{ RMenu },
166 => %W{ Back },
167 => %W{ Forward },
168 => %W{ Refresh },
169 => %W{ Stop },
170 => %W{ Search },
171 => %W{ Favorites },
172 => %W{ Home },
176 => %W{ Forward },
177 => %W{ Reverse },
178 => %W{ Stop },
179 => %W{ Play },
186 => %W{ ; :},
187 => %W{ = +},
188 => %W{ , <},
189 => %W{ - _},
190 => %W{ . >},
191 => %W{ / ?},
192 => %W{ ' ~},
219 => %W| [ {|,
220 => %W{ \ |},
221 => %W| ] }|,
222 => %W{ ' Quotes},
}

def keyscan_extract(buffer_data)
outp = ""
buffer_data = [buffer_data].pack("H*")
buffer_data.unpack("n*").each do |inp|
fl = (inp & 0xff00) >> 8
vk = (inp & 0xff)
kc = VirtualKeyCodes[vk]
f_shift = fl & (1<<1)
f_ctrl = fl & (1<<2)
f_alt = fl & (1<<3)
if(kc)
name = ((f_shift != 0 and kc.length > 1) ? kc[1] : kc[0])
case name
when /^.$/
outp << name
when /shift|click/i
when 'Space'
outp << " "
else
outp << " <#{name}> "
end
else
outp << " <0x%.2x> " % vk
end
end
return outp
end

# hex stream
msg = "0000010b000000010000001b000100017374646170695f75695f6765745f6b657973000000002900010002313136393934363536363930363631313536343531313036333437353038313600000000b300010bb901010101014e014f015401450101014e014f0154014501db01500141010801080108015001410144010d010d010d010d010d010d0101031003a003530131031003a00342034e035201310139031003a003430144031003a00358035203550141031003a003470101014c015a0141031003a00358034e034f01420133031003a0035201300141031003a00347035603470142031003a003470346014e010d010d010d010d010d010d010d000000000c0002000400000000"
puts keyscan_extract(msg)

extracting the buffer reveals a base64 encoded string. but unfortunately it’s not our flag

1
2
3
4
5
6
7
~/Desktop/kpmg/serious_traffic ⌚ 17:52:03
$ ruby keyscan_extract.rb
<0x00> <0x0b> <0x00> <0x00> <Esc> <F5> <N1> <N9> <F6> <Sleep> <N5> <Sleep> <N5> <F4> <0x00> <0x00> <0x00> <0x00> !6966(6!5$10#701 <0x00> <0x00> <Play> <0xb9> notenote[pa <Back> <Back> <Back> pad <Return> <Return> <Return> <Return> <Return> <Return> S1BNR19CdXRUaGlzaXNOb3R0aGVGbGFn <Return> <Return> <Return> <Return> <Return> <Return> <Return> <0x00> <0x00> <0x00> <0x00> <0x00> <0x00>

~/Desktop/kpmg/serious_traffic ⌚ 17:52:28
$ echo "S1BNR19CdXRUaGlzaXNOb3R0aGVGbGFn" | base64 -d
KPMG_ButThisisNottheFlag

now looking at the custom mimikatz command, we can see another base64 encoded string

mimikatz

cleaning and decoding it reveals our flag

1
2
3
~/Desktop/kpmg/serious_traffic ⌚ 17:56:04
$ echo "S1BNR3tNaW1pS0B0el8hNV9AdzM1MG5uZX0=" | base64 -d
KPMG{MimiK@tz_!5_@w350nne}

hard_crypto

the challenge consist of two files - a text file containing description of the challenge and a password protected zip file

1
2
3
4
5
6
7
8
9
10
11
12
13
14
~/Desktop/kpmg/hard_crypto ⌚ 8:54:16
$ ls -la
total 168
drwxrwxr-x 2 mkhdznfq mkhdznfq 4096 Nov 28 20:35 .
drwxrwxr-x 11 mkhdznfq mkhdznfq 4096 Nov 28 17:59 ..
-rw-rw-r-- 1 mkhdznfq mkhdznfq 156436 Nov 21 02:25 file
-rw-rw-r-- 1 mkhdznfq mkhdznfq 658 Nov 21 02:33 hard_crypto.txt

~/Desktop/kpmg/hard_crypto ⌚ 8:54:19
$ cat hard_crypto.txt
The Umbrella corp decided to upgrade the secret information exchange and communication to something so-called more efficient and simple to be implemented...

You are able to tap that piece of information and grabbed a file from the network...
KBCVEQSWIVCSWTTKIEYU2VCRPJHEITJQJZ5FM3KOIRETCTSEIJUE2VCBPFHHUQTLJZLUK6CZNJATATKUNN4FUVCGNFHGUWL2JZCFE2COKRKXQWTKIU2U2V2JO5GVITTLJVKGOMKZPJATKTSHJUZU4VCZPBGVIWL2JVKEC6KONJATGT2UJUYE2R2FO5MWUQTIKBBTSRKRKZJEEUDKPBGFEVTLFNGXUQL2JVCE252NPJAXUTKEJV3U26SBPJGUITLXJV5ECM2NIRKXSTT2NMYU4VCWNBHGUZZRJZVFS6KNNJKTGTTKJZWU22SRGJMVISLYJZWVCMCZNJMTCTL2NMZU6RCWNBHHU2ZRJZCEU22OKRMTEWSUMMZU4R2RPFNEIWTIJZDVKM2PIRITAUCDHFGFEVTLFM

the last line of the text file was encoded with base32. decoding it will expose a base64 encoded string

1
2
3
4
~/Desktop/kpmg/hard_crypto ⌚ 9:15:32
$ echo -n "KBCVEQSWIVCSWTTKIEYU2VCRPJHEITJQJZ5FM3KOIRETCTSEIJUE2VCBPFHHUQTLJZLUK6CZNJATATKUNN4FUVCGNFHGUWL2JZCFE2COKRKXQWTKIU2U2V2JO5GVITTLJVKGOMKZPJATKTSHJUZU4VCZPBGVIWL2JVKEC6KONJATGT2UJUYE2R2FO5MWUQTIKBBTSRKRKZJEEUDKPBGFEVTLFNGXUQL2JVCE252NPJAXUTKEJV3U26SBPJGUITLXJV5ECM2NIRKXSTT2NMYU4VCWNBHGUZZRJZVFS6KNNJKTGTTKJZWU22SRGJMVISLYJZWVCMCZNJMTCTL2NMZU6RCWNBHHU2ZRJZCEU22OKRMTEWSUMMZU4R2RPFNEIWTIJZDVKM2PIRITAUCDHFGFEVTLFM" | base32 -d | base64 -d
base32: invalid input
<DATA>60514343475f42540a10270d5a1b04191e1b66344a551f191b013d185c094c75611631026079340a0b0a</DATA><KEY>30303030303030303030705279555a68566225763f246a216d4b6539785a79542d566e774d2d6a4e7844</KEYbase64: invalid input

the encoded text was actually not padded correctly but alas, we still get what we wanted

1
2
<DATA>60514343475f42540a10270d5a1b04191e1b66344a551f191b013d185c094c75611631026079340a0b0a</DATA>
<KEY>30303030303030303030705279555a68566225763f246a216d4b6539785a79542d566e774d2d6a4e7844</KEY>

the values of data and key looks like it was hex encoded, but decoding it will only spit out gibberish value

1
2
3
4
5
6
7
8
9
10
~/Desktop/kpmg/hard_crypto ⌚ 9:16:10
$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> "60514343475f42540a10270d5a1b04191e1b66344a551f191b013d185c094c75611631026079340a0b0a".decode("hex")
"`QCCG_BT\n\x10'\rZ\x1b\x04\x19\x1e\x1bf4JU\x1f\x19\x1b\x01=\x18\\\tLua\x161\x02`y4\n\x0b\n"
>>> "30303030303030303030705279555a68566225763f246a216d4b6539785a79542d566e774d2d6a4e7844".decode("hex")
'0000000000pRyUZhVb%v?$j!mKe9xZyT-VnwM-jNxD'
>>>

looking closely, the values was at the same length. maybe they are xored together

1
2
3
4
5
6
7
8
9
10
~/Desktop/kpmg/hard_crypto ⌚ 9:27:28
$ python
Python 2.7.12 (default, Nov 19 2016, 06:48:10)
[GCC 5.4.0 20160609] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> data = "60514343475f42540a10270d5a1b04191e1b66344a551f191b013d185c094c75611631026079340a0b0a".decode("hex")
>>> key = "30303030303030303030705279555a68566225763f246a216d4b6539785a79542d566e774d2d6a4e7844".decode("hex")
>>> ''.join( chr( ord(a) ^ ord(b) ) for a,b in zip(data, key) )
'Password: W_#N^qHyCBuqu8vJX!$S5!L@_u-T^DsN'
>>>

JACKPOT! now we can use the password to extract the zip file

archive

the extracted file was not recognized when it was run though file command. looking at its hexdump, I was able to recognize it as a jpeg file from the 0xffdb marker

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
~/Desktop/kpmg/hard_crypto (copy) ⌚ 9:43:57
$ file 5892347529374598234
5892347529374598234: data

~/Desktop/kpmg/hard_crypto (copy) ⌚ 9:44:41
$ xxd 5892347529374598234| head
00000000: 0048 0000 ffdb 0043 0001 0101 0101 0101 .H.....C........
00000010: 0101 0101 0101 0101 0101 0101 0101 0101 ................
00000020: 0101 0101 0101 0101 0101 0101 0101 0101 ................
00000030: 0101 0101 0101 0101 0101 0101 0101 0101 ................
00000040: 0101 0101 0101 0101 01ff db00 4301 0101 ............C...
00000050: 0101 0101 0101 0101 0101 0101 0101 0101 ................
00000060: 0101 0101 0101 0101 0101 0101 0101 0101 ................
00000070: 0101 0101 0101 0101 0101 0101 0101 0101 ................
00000080: 0101 0101 0101 0101 0101 0101 0101 ffc0 ................
00000090: 0011 0804 1a06 9003 0111 0002 1101 0311 ................

now let’s create a dummy jpg file with imagemagick and use that to append the missing header

1
2
3
4
5
6
7
8
9
~/Desktop/kpmg/hard_crypto (copy) ⌚ 9:44:44
$ convert xc:white blank.jpg

~/Desktop/kpmg/hard_crypto (copy) ⌚ 9:50:08
$ (head -c 16 blank.jpg && cat 5892347529374598234) > fixed.jpg

~/Desktop/kpmg/hard_crypto (copy) ⌚ 9:51:21
$ file fixed.jpg
fixed.jpg: JPEG image data, JFIF standard 1.01, aspect ratio, density 1x72, segment length 16, baseline, precision 8, 1680x1050, frames 3

viewing the image, we could see a qr code was embedded in the top right corner

fixed image

after a little processing in gimp(crop, invert color and change input level) just upload it to an online qr decoder to get “STEGHIDE_PASS:5aZ=zrd+AQ!hf8qL”. looks like it’s a password to an embedded data in the image. now extract the data using steghide to get a text file containing the flag:

1
2
3
4
5
6
7
~/Desktop/kpmg/hard_crypto ⌚ 13:05:28
$ steghide extract -sf fixed.jpg -p "5aZ=zrd+AQ\!hf8qL"
wrote extracted data to "galF.txt".

~/Desktop/kpmg/hard_crypto ⌚ 13:05:48
$ cat galF.txt
KPMG{Dv69u8@pwKGccGNBhM&T32Bn$d4hX4Sm}