본문 바로가기
Write-up

[Crypto] 2019 CTFZONE CSES : CBC모드에서 KEY=IV 일때, 발생하는 취약점

by dladbru 2019. 12. 2.

문제 파일 하단에 있는 수상한 값들

 

먼저 루아와 관련된 문자열이 상당히 많이 존재하는 ELF파일이 주어졌다. 해당 파일을 살펴보는게 먼저이지만, 이 문제는 그것이 핵심이 아닌듯하니 생략하겠다.

 

단순한 루아 스크립트가 아닌 스크립트의 실행이 가능한 파일이라는 것을 알 수 있었다.

아래의 루아 라이브러리에 대해 빌드하면 나오는 형태와 비슷한 것 같다.

https://empier.tistory.com/334

 

파일의 하단부분인 0x37020 주소에 47과 4F가 많이 나타나는 수상한 값을 찾을 수 있었다.  읽을 수 없도록 꼬아져있다고 생각했다.

 

꼬아져있는 스크립트부분을 복호화하는 함수

 

a1은 암호화되어 있는 스크립트의 주소, a2는 스크립트의 길이를 인자로 복호화 연산을 하는 것으로 추정되는 함수를 찾아서 아래처럼 동일하게 작성해 동작시켰다.

 

[C] 복호화 코드

 

 

출력 결과로 루아 컴파일러에 의해 컴파일된 파일을 얻을 수 있었으며, 분홍색으로 하이라이트된 부분으로 5.3버전으로 빌드되었음을 알 수 있었다.

 

unluac_5.3.jar
0.31MB

 

5.3을 지원하는 루아 디컴파일러로 스크립트 형태로 얻어낼 수 있다.

 

local socket = require("socket")
local host, port = "re-cses.ctfz.one", 3607
local tcp = assert(socket.tcp())
function ksa(key)
  local key_len = string.len(key)
  local S = {}
  local key_byte = {}
  for i = 0, 255 do
    S[i] = i
  end
  for i = 1, key_len do
    key_byte[i - 1] = string.byte(key, i, i)
  end
  local j = 0
  for i = 0, 255 do
    j = (j + S[i] + key_byte[i % key_len]) % 256
    S[i], S[j] = S[j], S[i]
  end
  return S
end
function prga(S, text_len)
  local i = 0
  local j = 0
  local K = {}
  for n = 1, text_len do
    i = (i + 1) % 256
    j = (j + S[i]) % 256
    S[i], S[j] = S[j], S[i]
    K[n] = S[(S[i] + S[j]) % 256]
  end
  return K
end
function output(S, text)
  local len = string.len(text)
  local c
  local res = {}
  for i = 1, len do
    c = string.byte(text, i, i)
    res[i] = string.char(bxor(S[i], c))
  end
  return table.concat(res)
end
function base64_dec(data)
  local b = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"
  data = string.gsub(data, "[^" .. b .. "=]", "")
  return (data:gsub(".", function(x)
    if x == "=" then
      return ""
    end
    local r, f = "", b:find(x) - 1
    for i = 6, 1, -1 do
      r = r .. (f % 2 ^ i - f % 2 ^ (i - 1) > 0 and "1" or "0")
    end
    return r
  end):gsub("%d%d%d?%d?%d?%d?%d?%d?", function(x)
    if #x ~= 8 then
      return ""
    end
    local c = 0
    for i = 1, 8 do
      c = c + (x:sub(i, i) == "1" and 2 ^ (8 - i) or 0)
    end
    return string.char(c)
  end))
end
local bit_op = {}
function bit_op.cond_and(r_a, r_b)
  return r_a + r_b == 2 and 1 or 0
end
function bit_op.cond_xor(r_a, r_b)
  return r_a + r_b == 1 and 1 or 0
end
function bit_op.cond_or(r_a, r_b)
  return r_a + r_b > 0 and 1 or 0
end
function bit_op.base(op_cond, a, b)
  if a < b then
    a, b = b, a
  end
  local res = 0
  local shift = 1
  while a ~= 0 do
    r_a = a % 2
    r_b = b % 2
    res = shift * bit_op[op_cond](r_a, r_b) + res
    shift = shift * 2
    a = math.modf(a / 2)
    b = math.modf(b / 2)
  end
  return res
end
function bxor(a, b)
  return bit_op.base("cond_xor", a, b)
end
function band(a, b)
  return bit_op.base("cond_and", a, b)
end
function bor(a, b)
  return bit_op.base("cond_or", a, b)
end
function rc4_cipher(key, text)
  local text_len = string.len(text)
  local S = ksa(key)
  local K = prga(S, text_len)
  return output(K, text)
end
function print_result(buffer)
  tcp:send(buffer .. "\n")
  local msg, status, partial = tcp:receive()
  if msg == nil then
    msg = "incorrect input\n"
  end
  print(msg)
end
function print_help()
  print("\n")
  print("+--------------------------------+")
  print("|    Secure encryption system    |")
  print("+--------------------------------+")
  print("| encryption: encrypt:: |")
  print("| decryption: decrypt:: |")
  print("|                                |")
  print("| exit: q                        |")
  print("+--------------------------------+")
end
function handle_answer(answer)
  enc_index = string.find(answer, "encrypt::")
  dec_index = string.find(answer, "decrypt::")
  command = rc4_cipher("ctfzone2019", base64_dec("1bC87lzEebgL"))
  bin_index = string.find(answer, command)
  if enc_index == 1 or dec_index == 1 or bin_index == 1 then
    print_result(answer)
  end
end
function main()
  local answer
  tcp:settimeout(1)
  tcp:connect(host, port)
  repeat
    print_help()
    io.write([[

Enter your command: ]])
    io.flush()
    answer = io.read()
    handle_answer(answer)
  until answer == "q"
end
main()

 

server
0.01MB

여기서 난 풀린 줄 알았다 -_- encrypt::, decrypt:: gpsjdeadk 3가지 명령이 존재했으며 gpsjdeadk는 서버파일을 전달해주는 커맨드였다. 

 

rc4_cipher(ctfzone2019,base64_dec("1bC87lzEebgL") = gpsjdeadk

 

 

서버파일을 열어보니 플래그를 "ctfzoneencaeskey" 키로 ECB암호화해 주소 0x2030D0의 구렁텅이로 집어넣는다.

 

encrypt:: decrypt:: 사용 분기

 

암호화코드 부분

그러면 이 구렁텅이를 어디서쓰는데라는 의문을 품고 찾아보니 encrypt, decrypt에 키값과 IV값으로 사용되고 있었다.

키와 IV를 같은 형식으로 사용하는것이 관건일 것이라고 생각은했다. 

 

 

일반적으로 CBC모드는 키와 IV값으로 연산하고 다음 블럭에서 사용되기때문에 강력하다 생각하기 때문에 취약점이 없을줄 알았다.

 

그러던 중에 팀원이 링크를 하나주시며, 이거랑 너무나 비슷해보인다하셨다.

https://ctftime.org/writeup/14611

 

해당 링크를 요약하면 아래와 같다.

1. Create a three block (48 byte) plaintext, note C0 , C1 , and C2
2. Modify the ciphertext such that C0 = C2 to force DK(C2) = DK(C0)
3. Decrypt modified plaintext to obtain P0, P2, P2 for the modified block
4. Compute DK(C0) using P2 = DK(C2) ⊕ C1 ⇒ P2 ⊕ C1 = DK(C2)
5. Compute IV = P1 ⊕ DK(C0)
6. IV = Key, which contains the flag

 

3,4번에 대해서 이해가 잘되지는 않는다 하지만 아래코드를 보면 이해가 된다. 일단 아래 상황을 알아두면 좋을 것 같다 끗.

 

from pwn import *
from base64 import b64decode, b64encode
from Crypto.Cipher import AES

key = "ctfzoneencaeskey"

p = remote('re-cses.ctfz.one',3607)

p.send("encrypt::"+"A"*48)
enc = p.recvline()[:-1]
# print 'original: '+enc
enc = b64decode(enc)
c0 = enc[:16]
c1 = enc[16:32]
c2 = enc[32:]
res = b64encode(c0+c1+c0)
# print 'modified: '+res
p.sendline("decrypt::"+res)
leak = p.recv().replace('\x00','')
leak = p.recv().replace('\x00','')
# print leak

p0 = leak[:16]
p1 = leak[16:32]
p2 = leak[32:]

# print"\n".join([c0,c1,c2])

DK = ''
for x,y in zip(p2,c1):
    DK += chr(ord(x)^ord(y))

log.info(DK)

iv = ''
for x,y in zip(p0,DK):
    iv += chr(ord(x)^ord(y))

log.success(b64encode(iv))

if AES.new(iv,AES.MODE_CBC,iv).encrypt('A'*48) == enc:
    pass
else:
    print b64encode(AES.new(iv,AES.MODE_CBC,iv).encrypt('A'*48))
    print b64encode(enc)

log.success(AES.new(key,AES.MODE_ECB).decrypt(iv))

Flag: ctfzone{h4hahahah_k3y=1v}

반응형

댓글