PCTF 2018 - macsh(Crypto)

문제 파일 (handout-af78913b8dd5e7dfca5102ad99f4bfb262ceefd5.tgz)

우리가 원하는 코드와 그 코드의 fmac값을 같이 입력하면 그 코드를 실행해주는 프로그램이다.
일단 잠시 소스의 일부를 봐보자

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
def to_block(b):
return bytes.fromhex('{:0{width}x}'.format(b, width=N*2))

def xor(x, y):
return bytes([xe ^ ye for xe,ye in zip(x,y)])

def to_blocks(m):
m += to_block(len(m))
padb = N - len(m) % N
m += bytes([padb]) * padb
blocks = [m[N*i : N*(i+1)] for i in range(len(m) // N)]
return blocks

def rot(n, c):
return (n >> c) | ((n & ((1 << c) - 1)) << (8 * N - c))

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)

[입력 값] + [입력한 값의 length (16 bytes)] + [padding (N - length bytes)]
의 방법으로 블록이 만들어지고 있다.

fmac의 마지막에는 reduce를 사용하여 모든 블록을 xor한 값을 리턴해주고 있다.

이때, f(k0, i)잘 봐보면 to_block(rot(to_int(k0), i % (8 * N)))로 되어있다.
N = 16이므로 f(k0, 0) == f(k0, 128)이라는 결론이 나온다.

즉, reduce에서 xor을 하며 합칠 때, 같은 입력값으로 256블록이 만들어진다면 서로 같은값끼리 xor돼서 256블록의 xor결과는 0이 되게 된다. 그러면 그 이후의 블록들의 xor결과만 남게 된다.

그럼 이제 tag를 이용해서 fmac을 생성해주는 부분과 우리가 실제로 입력한 값을 검증하는 부분의 차이점을 봐보자.

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")

실제로 검증할때

1
2
3
4
5
6
7
8
9
10
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")

tag명령어는 cmd가 privileged에 속하지만 않으면 되고, 공백을 기준으로 cmdline이 만들어진다.

하지만 실제로 검증할때는 공백을 포함한다.

그래서 tag명령어로 256블록을 A로 가득 채운뒤, 뒤에 원하는 값을 채우면 원하는 값의 fmac값을 알아올 수 있다.

하지만, 이때의 주의점은 length가 우리가 입력한 만큼 증가된 상태이기 때문에 실제로 검증하는 부분에는 공백을 그만큼 붙여줬다.

공백을 기준으로 cmd, args로 나누기 때문에 앞에 공백을 붙이는건 문제가 되지 않는다.

solve.py

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
from pwn import *

r = remote('macsh.chal.pwning.xxx', 64791)

r.recv()

cmd = 'cat flag.txt'
cmdline = 'A' * 16 * 256 + cmd
r.sendline(' <|>tag ' + cmdline)
mac = r.recv().split()[0]
print mac

cmdline = ' ' * 16 * 256 + cmd
r.sendline(mac + '<|>' + cmdline)

print r.recv()
Share