[2018_AceBear_CTF] STEg Challenge(996)

Updated:

Exercise

Download: Link Service: nc 35.200.176.244 33338 author: komang4130

[그림] exercise

Solution

AceBear CTF 에서 나왔던 Forensic/MISC 분야 중, 마지막 풀이 문제. JS 문제랑 마찬가지로 대회 당시에는 해결하지 못했다. 문제 제목에 대해서 조금만 더 고찰을 했었더라면 해결할 수 있었을텐데…

link를 통해, 받은 rar 파일을 압축 해제하면, flag_enc.png 파일을 하나 확인할 수 있다.

[그림] flag_enc.png

이미지를 자세히 보면, 좌측 상단에 검정색 pixel 일부를 볼 수 있다. 당연히, pixel을 이용한 steganography 방식의 LSB 로 인식하고 접근했었는데 별짓을 다해도 의미있는 문자열을 확인할 수 없었던게 큰 문제였던 것 같다.

문제를 보면, nc 로 socket 접속할 수 있는 server 가 하나 주어지는데, 접속해보면 아래와 같이 16byte의 data를 받아 base64로 encode 된 data를 receive할 수 있었다.

[그림] encode data

decode 하여, 확인해보면 flag_enc와 동일한 이미지인 것을 확인할 수 있는데, 자세히 보면 상단의 pixel 위치가 조금씩 다르다.

[그림] decode data

여기서 생각의 전환이 조금 필요했다. server로 부터 어떤 유의미한 데이터를 얻어낼 수 있는가 고민을 하다가 결국, 원점으로 돌아가 flag_enc 로부터 정보를 얻어내려 했던 것이 발목을 잡았다..

결과적으로, 문제 제목에서 알 수 있듯이 STEg, flag_enc 라는 문자열에 주목해보자. enc라는 문자열은 무엇인가 encrypt 되었다는 것을 의미한다. 그렇다면, 어떤 로직을 통해 data가 encrypt 되었다는 것인데 pixel과 Image를 Steganography 적용하는 방식에서 사용될 수 있는 항목은 주로 lsb일 것이다.

기존에 알고 있던 lsb 방식은 모두 이용해 보았지만 별 소득은 없었고, 구글을 뒤지다가 Python 과 관련된 도구에서 Stepic라는 것을 찾을 수 있었다. (Python Steganography 라는 언급만 있었어도…ㅂㄷㅂㄷ)

[그림] stepic stegano

연속된 3개의 pixel 에 data를 은닉하는 방식의 lsb 인데, code 그대로 사용해 unhide 할 수 있다. (https://stackoverflow.com/questions/16838341/steganography-in-python-stepic)

from PIL import Image

def decode_imdata(imdata):
    '''Given a sequence of pixels, returns an iterator of characters
    encoded in the image'''

    imdata = iter(imdata)
    while True:
        pixels = list(imdata.next()[:3] + imdata.next()[:3] + imdata.next()[:3])
        byte = 0
        for c in xrange(7):
            byte |= pixels[c] & 1
            byte <<= 1
        byte |= pixels[7] & 1
        yield chr(byte)
        if pixels[-1] & 1:
            break


im = Image.open('test')
data  = ''.join(decode_imdata(im.getdata())).decode('hex')
print data


위 code로 서버로부터 받은 encode dataflag_enc.png를 unhide 했을 때, 아래와 같은 문자열을 얻을 수 있었다. 무엇인가 복호화가 된다는 사실이 일단 실마리이다.

 jsh05042@Macs-MacBook-Pro  ~/Desktop/CTF/2018_Acebear_CTF/Forensic:MISC/STEg
 python ./steg.py
|vohenrbuq{nxe
 jsh05042@Macs-MacBook-Pro  ~/Desktop/CTF/2018_Acebear_CTF/Forensic:MISC/STEg
 python ./steg.py
53:)~>1&v1z4hu

굳이, server로부터 encode data를 주는 이유는 무엇일까? 해당 data로부터 알아내야할 것이 있다는 말이다. flag_enc.png로부터 unhide 했을 때, 별 소득이 없었으므로 해당 결과 값에 영향을 미칠만한 항목을 찾아냈어야 했다. (이 부분에서도 꽤 많은 시간을 쏟았다.)

여러 data 를 server 로 send 하다가, \x00 로만 16byte를 채워보내고 받은 encode data에 대해서 unhide 한 결과 식별할 수 있는 string(OMG_YOU_BREAK_IT)을 얻을 수 있었다.

jsh05042@Macs-MacBook-Pro  ~/Desktop/CTF/2018_Acebear_CTF/Forensic:MISC/STEg
 python -c “print ‘\x00’*16” | nc localhost 33338 | base64 -D > tmp.png

jsh05042@Macs-MacBook-Pro  ~/Desktop/CTF/2018_Acebear_CTF/Forensic:MISC/STEg
 python ./steg.py
OMG_YOU_BREAK_IT

또 여기서, 삽질했던게 Null로 얻은 문자열 길이는 16byte인데 flag_enc.png 에서 얻은 문자열 길이는 14byte이다.. 기왕 내는거 길이 좀 맞춰서 내지;; 14byte 까지만 각각의 문자열을 xor 할 경우 flag를 확인할 수 있었다;;;

from PIL import Image

def decode_imdata(imdata):
    '''Given a sequence of pixels, returns an iterator of characters
    encoded in the image'''

    imdata = iter(imdata)
    while True:
        pixels = list(imdata.next()[:3] + imdata.next()[:3] + imdata.next()[:3])
        byte = 0
        for c in xrange(7):
            byte |= pixels[c] & 1
            byte <<= 1
        byte |= pixels[7] & 1
        yield chr(byte)
        if pixels[-1] & 1:
            break

def strxor(a,b):
    return ''.join(chr(ord(x)^ord(y)) for (x,y) in zip(a,b))


im = Image.open('flag_enc.png')
data  = ''.join(decode_imdata(im.getdata())).decode('hex')
print data


im2 = Image.open('tmp.png')
data2 = ''.join(decode_imdata(im2.getdata())).decode('hex')
print data2


print ''.join(chr(ord(x)^ord(y)) for (x,y) in zip(data,data2))
jsh05042@Macs-MacBook-Pro  ~/Desktop/CTF/2018_Acebear_CTF/Forensic:MISC/STEg
 python ./steg.py
53:)~>1&v1z4hu
OMG_YOU_BREAK_IT
zStep1k_st3p1k!!

솔직히, 맞추라고 낸 문제인가 애매하다.. Forensic이 아니라 그냥 MISC 느낌.. 어거지로 끼워 만든 문제 느낌이 너무 강하고, 쓸데 없는 삽질이 너무 많이 필요했다…;;

AceBear{zStep1k_st3p1k!!}

Leave a comment