[Plaid CTF 2018] macsh - 125
|
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 |
사용자로부터 입력을 받아 <|>를 기준으로 mac과 cmdline으로 나누어준다.
그 후 cmdline을 또 공백을 기준으로 나누어 cmd와 args로 나눈다.
그 후 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값을 너헝 실행해주는데, 조건이 있다.
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번째 블록은 키가 같게 된다. 이걸 이용해서 이제 문제를 풀 수가 있다.
Enc(BLOCK_A | MyCommand | len(BLOCK_A | MyCommand) | padding)
Enc(tagCommand | len(tagCommand) | padding)
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 |