Maojui

PlaidCTF 2018 - Macsh (Crypto, 125)

2018-05-07

Challenge

1
2
Forget ssh, this is a much more secure shell. 
Server running at macsh.chal.pwning.xxx:64791

This Challenge need you bypass the MAC check.

1
2
mac, cmdline = input().split('<|>')
cmd, *args = cmdline.split()

What you need to send is like MAC<|>COMMAND

And the MAC check is …

1
2
if cmd == "tag" or bytes.hex(fmac(k0, k1, encode(cmdline))) == mac:
eval(cmd)(*args)

If you want server executes your command :

  1. cmd == ‘tag’
  2. The right MAC, encode by cmdline.

Let’s look at how MAC generate :

1
2
3
4
5
6
7
8
9
10
# Noted that AES.MODE_ECB -> Cut & Paste Directly LoL
def fmac(k0, k1, m):
C = AES.new(k1, AES.MODE_ECB)
# Message come here will transform to block(16 bytes for each), encrypt it with index,
# and finally XOR all of them !!
bs = [C.encrypt(xor(b, f(k0, i))) for i,b in enumerate(to_blocks(m))]
return reduce(xor, bs, b"\x00" * N)

def f(k0, i):
return to_block(rot(to_int(k0), i % (8 * N)))

Here stop me at first, because of C.encrypt(xor(b, f(k0, i))), the index is considered.
So, is Cut & Paste fail … ?

Fortunately, I found that the point is in f(k0,i), i % (8 * N)

Index will reset if i > 8 * blocksize (2048 bytes).

Solved

Noted that in tag function :

1
2
3
...
cmdline = encode(" ".join([cmd] + list(args)))
...

Although This funcion will print the MAC of followed command.
Command after tag will be split, and concat with ‘ ‘, but MAC check will not.

E.G.

1
2
3
4
5
6
7
8
9
10
11
Send : "anything<|>tag echo           a"

Server get :
MAC = anything
cmd = tag
args = "echo a"
This will be pass to tag function and return MAC('echo a')

So if you Send the mac you just get from server :
"(MAC back)<|>echo a" -> This will be Failed.
because of MAC('echo a') != MAC('echo a')

This, devil space waste me half an hour QwQ.

Therefore, make the payload be carefully XD.

E.G.

Red one is what I wanted.
Blue one & Green one is all garbage

And I want the MAC of “ pwd” -> MAC( block(‘ pwd’ + padding) + block(length(4)+padding) )

block :
b’\x91\x04\xa61\xbb7\xe7\x07S(k*|~\xb0\xa8’
b’u-\xaeD\n\xe4\xdd\xad\xb2%\xe7\xad0HU\x9b’

———————— START ———————————-

Send : mac <|> tag aaaa....2048(total).....aaaaa pwd

-> block will like :
b’\xef\xb9\xaa\xe5\xe4}\xa4\x9e\xed\xdc\xa6\xc9b\xb8\xdb\x03’
b”\xff\xd4\xbbL!\xcc’\x82\x19\xdf\xd5\xacy\xbd\xa3\xa1”

b’\x94\xa9\xad{\xc1J(D7$\x06Q\x06\x0c\xff>’
b’\x91\x04\xa61\xbb7\xe7\x07S(k*|~\xb0\xa8’
b’\xf8\x93\xa7ag\xdc\xf1\xe3H;@\xa4/D}\xb2’

-> return MAC( block(a*16) * 128 + block(‘ pwd’ + padding) + block(length(2052)+padding) )
-> MAC : 296e6760be51d89e7003a67491664baf

Send : mac <|> tag aaaa....2048(total).....aaaaaecho

-> block :
b’\xef\xb9\xaa\xe5\xe4}\xa4\x9e\xed\xdc\xa6\xc9b\xb8\xdb\x03’
b”\xff\xd4\xbbL!\xcc’\x82\x19\xdf\xd5\xacy\xbd\xa3\xa1”

b’\x94\xa9\xad{\xc1J(D7$\x06Q\x06\x0c\xff>’
b’\xd4Fw\xca@\x9a\xe5\xd1\x077\xc2\rHz}\xbb’
b’\xf8\x93\xa7ag\xdc\xf1\xe3H;@\xa4/D}\xb2’

-> return MAC( block(a*16) * 128 + block(‘echo’ + padding) + block(length(2052)+padding) )
-> MAC : a14caecaef319d3668953429aaaeaed9

Send : mac <|> tag echo
-> block:
b’\xd4Fw\xca@\x9a\xe5\xd1\x077\xc2\rHz}\xbb’
b’u-\xaeD\n\xe4\xdd\xad\xb2%\xe7\xad0HU\x9b’

-> return MAC( block(‘echo’ + padding) + block(length(4)+padding) )
-> MAC : 83fd0846aafe9e30c710badab6b61a43

——————————- GET ALL ——————————-

XOR Three MAC,

296e6760be51d89e7003a67491664baf ^ a14caecaef319d3668953429aaaeaed9 ^ 83fd0846aafe9e30c710badab6b61a43

We then get a MAC of (“ pwd”)

-> bdfc1ecfb9edb98df8628878d7eff35

And then send back to server to execute the command, for example

bdfc1ecfb9edb98df8628878d7eff35<|> pwd

And again, be careful your devil space QwQ….

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def send_command(cmd):
conn.sendline('mac <|> tag '+'a'*2048 + cmd.rjust(4,' '))
t1 = int(conn.recvuntil('\n')[:-1],16)
conn.recv()
assert len(cmd)!= 5, 'sorry, this function can\'t do this.'
if len(cmd) > 5 :
echo = 'echo ' + 'a'*(len(cmd)-5)
else :
echo = 'echo'
conn.sendline('mac <|> tag '+'a'*2048 + echo)
t2 = int(conn.recvuntil('\n')[:-1],16)
conn.recv()

conn.sendline('mac <|> tag '+echo)
t3 = int(conn.recvuntil('\n')[:-1],16)
conn.recv()
conn.sendline('{}<|>{}'.format(hex(t1^t2^t3)[2:],cmd.rjust(4,' ')))
print(conn.recv())

Then, send the command solved this challenge LoL.

1
2
3
send_command('pwd')
send_command('ls .')
send_command('cat flag.txt')

github solve.py