먼저 루아와 관련된 문자열이 상당히 많이 존재하는 ELF파일이 주어졌다. 해당 파일을 살펴보는게 먼저이지만, 이 문제는 그것이 핵심이 아닌듯하니 생략하겠다.
단순한 루아 스크립트가 아닌 스크립트의 실행이 가능한 파일이라는 것을 알 수 있었다.
아래의 루아 라이브러리에 대해 빌드하면 나오는 형태와 비슷한 것 같다.
https://empier.tistory.com/334
파일의 하단부분인 0x37020 주소에 47과 4F가 많이 나타나는 수상한 값을 찾을 수 있었다. 읽을 수 없도록 꼬아져있다고 생각했다.
a1은 암호화되어 있는 스크립트의 주소, a2는 스크립트의 길이를 인자로 복호화 연산을 하는 것으로 추정되는 함수를 찾아서 아래처럼 동일하게 작성해 동작시켰다.
출력 결과로 루아 컴파일러에 의해 컴파일된 파일을 얻을 수 있었으며, 분홍색으로 하이라이트된 부분으로 5.3버전으로 빌드되었음을 알 수 있었다.
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()
여기서 난 풀린 줄 알았다 -_- encrypt::, decrypt:: gpsjdeadk 3가지 명령이 존재했으며 gpsjdeadk는 서버파일을 전달해주는 커맨드였다.
rc4_cipher(ctfzone2019,base64_dec("1bC87lzEebgL") = gpsjdeadk
서버파일을 열어보니 플래그를 "ctfzoneencaeskey" 키로 ECB암호화해 주소 0x2030D0의 구렁텅이로 집어넣는다.
그러면 이 구렁텅이를 어디서쓰는데라는 의문을 품고 찾아보니 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}
'Write-up' 카테고리의 다른 글
[Crypto] 2019 CTFZONE agents : OFB 모드에서 값을 조작해야하는 문제 (0) | 2019.12.02 |
---|---|
[Reversing]Defcon 2015 Pr0dk3y (2) | 2015.06.03 |
HISCHALL 2013 문제 풀이 Write up (5) | 2013.11.17 |
[HUST] 7. 수학 암호 (2) | 2012.12.03 |
[순천향대 2012 정보보호 페스티벌 본선 사회공학적 해킹] (0) | 2012.09.17 |
댓글