[Plaid CTF 2018] macsh - 125

2018. 5. 7. 10:05


 

fmac.py

macsh.py



PCTF의 125점짜리 Crypto문제이다. nc macsh.chal.pwning.xxx 64791 로 접속하면

사용자로부터 무언가를 입력받는다. 일단 아무거나 입력해보면 그냥 죽어버린다.



주어진 소스를 통해 서버에서 무슨 일을 하고 있는지 알 수 있다.

fmac.py는 keygen과 AES암호화로 암호하시키는 코드이고 macsh.py가 서버에서 정말로 하는 일이다. 코드를 보자


  • macsh.py

1
2
3
4
5
6
7
8
9
10
11
while True:
    print("|$|> ", end='', flush=True)
    mac, cmdline = input().split('<|>')
    cmd, *args = cmdline.split()
    if cmd not in commands:
        print("macsh: {}: command not found".format(cmd))
        continue
    if cmd == "tag" or bytes.hex(fmac(k0, k1, encode(cmdline))) == mac:
        eval(cmd)(*args)
    else:
        print("macsh: bad tag")
cs


사용자로부터 입력을 받아 <|>를 기준으로 maccmdline으로 나누어준다.

그 후 cmdline을 또 공백을 기준으로 나누어 cmdargs로 나눈다.

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


그 후 cmd가 commands에 포함되어 있는지 검사한다. commands에는 아래 명령들이 포함되어있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
commands = [
    "echo",
    "tag"
]
 
privileged = {
    "pwd",
    "cd",
    "ls",
    "cat"
}
 
commands.extend(privileged)
cs


즉 우리는 cmd값으로 echo, tag, pwd, cd, ls, cat 을 사용할 수 있다.

여기 cmd를 eval을 통해 argv값을 너헝 실행해주는데, 조건이 있다.


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


1. cmd == "tag"

2. crypt(cmdline) == mac


여기서 cmd가 tag이면 항상 tag를 실행하므로 두번째조건인 mac과 cmdline를 암호화한 것이 같으면 cmd를 원하는대로 설정해서 사용할 수 있을 것이다.


그럼 tag가 무엇을 해주느냐?


1
2
3
4
5
6
def tag(cmd, *args):
    if cmd not in privileged:
        cmdline = encode(" ".join([cmd] + list(args)))
        print(bytes.hex(fmac(k0, k1, cmdline)))
    else:
        print("macsh: tag: Permission denied")
cs


cmd가 echo이거나 tag일때만 실행되어 tag뒷부분을 cmdline으로 해서 fmac으로 암호화해서 돌려준다.

그러므로 아래와 같이 하면 echo가 실행되는 것을 알 수 있다.



tag echo AAAA

-> bytes.hex(fmac(k0, k1, "echo AAAA"))


이제 fmac의 암호화부분을 살펴보자.

1
2
3
4
5
6
7
def f(k0, i):
    return to_block(rot(to_int(k0), i % (8 * N)))
 
def fmac(k0, k1, m):
    C = AES.new(k1, AES.MODE_ECB)
    bs = [C.encrypt(xor(b, f(k0, i))) for i,b in enumerate(to_blocks(m))]
    return reduce(xor, bs, b"\x00" * N)
cs


암호화는 m을 블록으로 나누어 각 블록을 순서대로 암호화한다. 

이 때 key를 블록의 index만큼 rotation해서 암호화를 진행하고 암호화된 m의 블록들을 모두 xor하여 그 값을 반환한다. 


key가 계속 로테이션하면서 변하지만 rot(to_int(k0), i % (8 * N))이므로 (N=16)

1번째 블록과 129번째 블록은 키가 같게 된다. 이걸 이용해서 이제 문제를 풀 수가 있다.


  1. Enc(BLOCK_A MyCommand | len(BLOCK_A | MyCommand) | padding)

  2. Enc(tagCommand | len(tagCommand) | padding)

  3. Enc(BLOCK_A | tagCommand | len(BLOCK_A | tagCommand) | padding)

참고로 블록들은 뒤에 입력에 들어간 cmdline의 길이를 붙이고 padding은 PKCS#7으로 된다.

이제 저 1,2,3을 tag명령을 통해 구하여 XOR하게 되면, 먼저 1^3이 되서 len부터 뒷부분이 모두 없어지고

그 후 2와 xor되서 tagCommand도 없어져 Enc(BLOCK_A MyCommand | len(MyCommand) | padding) 를 얻게 된다.


아래를 공격코드이다.


  • xor3.py

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
import binascii
import string
import sys
import os
 
def encode(cmdline):
    return cmdline.encode('utf-8')
 
def xor(x, y):
    return bytes([xe ^ ye for xe,ye in zip(x,y)])
    
def xor3(x,y,z):
    x=binascii.unhexlify(encode(x))
    y=binascii.unhexlify(encode(y))
    z=binascii.unhexlify(encode(z))
    res = xor(bytes(x),bytes(y))
    res = xor(bytes(res),bytes(z))
    return res
 
x=sys.argv[1]
y=sys.argv[2]
z=sys.argv[3]
 
res = xor3(x,y,z)
print(str(binascii.hexlify(res))[2:-1])
 
 
cs

  • hand_attack.py

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
#!/usr/bin/env python
from pwn import *
import binascii
import string
import sys
import os
from subprocess import Popen, PIPE
 
conn = remote('macsh.chal.pwning.xxx'64791)
 
BLOCK_A = "tag "+"A"*124
BLOCK_A = BLOCK_A*128
 
pwd ="/home/macsh"
flag = "flag.txt"
 
BLOCK_COMMAND=["pwd","ls /home/macsh""cat flag.txt"]
 
for BLOCK_B in BLOCK_COMMAND:
    PAD = "A"*len(BLOCK_B)
    ##########  step.1
    print(conn.recvuntil('|$|>'))
    l="asdf<|>tag "+BLOCK_A+BLOCK_B
    conn.sendline(l)
 
    x1 = conn.recvline().strip()
    #print(x1)
    ##########  step.2
    print(conn.recvuntil('|$|>'))
    l="asdf<|>tag "+PAD
    conn.sendline(l)
 
    x2 = conn.recvline().strip()
    #print(x2)
    ##########  step.3
    print(conn.recvuntil('|$|>'))
    l="asdf<|>tag "+BLOCK_A+PAD
    conn.sendline(l)
 
    x3=conn.recvline().strip()
    command = "python3 xor.py "+x1+" "+x2+" "+x3
    popen = Popen(command, shell=True, stdout=PIPE)
    output, error = popen.communicate()
 
    output=output.strip()
    l=output+"<|>"+BLOCK_B
    print(l)
    conn.sendline(l)
    print(conn.recvline())
 
conn.interactive()
cs



PCTF{fmac_is_busted_use_PMAC_instead}


대회가 다 끝난 시점에 스샷을 찍었다. 풀었을 때 대략 40명가량이었는데 대회가 끝났을 쯤엔 100명이 넘어가 있었다...








'Write-up > Crypto' 카테고리의 다른 글

[UIUCTF 2018] Hastad  (0) 2018.06.02
[Byte Bandits CTF 2018]R u Ronald Rivest?  (0) 2018.06.02
[0ctf 2017] integrity - 75p  (0) 2018.05.02
[Byte Bandits CTF 2018] R u Ronald Rivest? 70p  (0) 2018.04.27
[UIUCTF 2018] xoracle (250)  (0) 2018.04.18

+ Recent posts