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명령어는 cmdprivileged에 속하지만 않으면 되고, 공백을 기준으로 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