문제페이지에 접속해보면
문자열을 입력받고 제출할 경우 img 태그가 삽입된다.
전체 html 소스를 확인하자.
<html> <head> <script src="jquery-3.3.1.min.js"></script> <script> var bytes = []; $.get("bytes", function(resp) { bytes = Array.from(resp.split(" "), x => Number(x)); }); function assemble_png(u_in){ var LEN = 16; var key = "0000000000000000"; var shifter; if(u_in.length == LEN){ key = u_in; } var result = []; for(var i = 0; i < LEN; i++){ shifter = key.charCodeAt(i) - 48; for(var j = 0; j < (bytes.length / LEN); j ++){ result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i] } } while(result[result.length-1] == 0){ result = result.slice(0,result.length-1); } document.getElementById("Area").src = "data:image/png;base64," + btoa(String.fromCharCode.apply(null, new Uint8Array(result))); return false; } </script> </head> <body> <center> <form action="#" onsubmit="assemble_png(document.getElementById('user_in').value)"> <input type="text" id="user_in"> <input type="submit" value="Submit"> </form> <img id="Area" src=""/> </center> </body> </html>
하나씩 확인해보자.
페이지가 로드되면 ajax 쿼리로 get 요청을 한 후, 그 결과를 bytes
에 저장한다.
var bytes = [];
$.get("bytes", function(resp) {
bytes = Array.from(resp.split(" "), x => Number(x));
});
그 후 폼이 입력되면 assemble_png
함수가 호출된다.
이 함수에서는 가장 먼저 입력값의 길이가 LEN
과 일치할 경우, 입력값을 key
로 설정한다.
function assemble_png(u_in){
var LEN = 16;
var key = "0000000000000000";
var shifter;
if(u_in.length == LEN){
key = u_in;
}
그리고 루프를 도는데 어떤 연산을 통해 오프셋을 설정한 뒤, 앞서 받아온 bytes
안에서 오프셋에 해당하는 값을 가져와서 result
에 저장한다.
var result = [];
for(var i = 0; i < LEN; i++){
shifter = key.charCodeAt(i) - 48;
for(var j = 0; j < (bytes.length / LEN); j ++){
result[(j * LEN) + i] = bytes[(((j + shifter) * LEN) % bytes.length) + i]
}
}
여기서 중요한 것은 오프셋이 설정되는 과정이다.
key
를 하나씩 가져와서, '0'(48)을 기준으로 차이나는 오프셋 크기를 shifter
로 설정한다.
그리고 그 shifter
를 이용해 bytes
에서 값을 가져오는 것이다.
마지막으로 위에서 만든 result
값을 png
이미지로 로드시킨다.
document.getElementById("Area").src = "data:image/png;base64," + btoa(String.fromCharCode.apply(null, new Uint8Array(result)));
'0'을 기준으로 오프셋을 설정하므로 key는 문자가 아닌 숫자로 구성된다는 것을 유추할 수 있다.
또한 만들어진 result
가 나타내는 png 파일은 첫 16바이트가 고정된다.
이것은 key
길이와 동일하다.
따라서, 10진수 범위 내에서 png 헤더의 첫 16바이트를 갖게하는 shifter
값을 찾는 방법으로 key
값을 구할 수 있게 된다.
res = requests.get('http://2019shell1.picoctf.com:57738/bytes')
data = res.text.split(' ')
expected = '89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52'.split(' ')
len_round = 16
key = []
def getKey():
for i in range(len_round):
k = []
for shifter in range(10):
result = data[((shifter * len_round) % len(data)) + i]
if int(result, 10) == int(expected[i], 16):
print('Key found : ' + result)
k.append(shifter)
key.append(k)
png헤더의 첫 16바이트를 만들어내는 key
는 두 개였다.
이제 이 key
를 이용해 이미지 파일을 실제로 생성해서, 올바른 이미지를 만드는 키 값을 찾아야 한다.
result = [0 for x in range(len(data))]
def decPNG(key, idx):
for i in range(len_round):
shifter = key[i]
for j in range(len(data) // len_round):
result[(j * len_round) + i] = int(data[(((j + shifter) * len_round) % len(data)) + i], 10)
n = 'flag{}.png'.format(idx)
try:
img = Image.open(io.BytesIO(bytes(result)))
img.save(n)
print ("Create png successfully [{}]".format(n))
except IOError:
print ("Failed to create png [{}]".format(n))
두 개중 하나만 제대로 된 이미지 파일을 생성했다.
oot@kali:/work/ctf/PICO2019/js_kiddie# python3 ex.py
Create png successfully [flag0.png]
Failed to create png [flag1.png]
생성된 이미지 파일은 qr코드였고, 해당 qr코드를 조회하면 플래그를 확인할 수 있다.
picoCTF{905765bf9ae368ad98261c10914d894e}
import io
import requests
import itertools
res = requests.get('http://2019shell1.picoctf.com:57738/bytes')
data = res.text.split(' ')
expected = '89 50 4E 47 0D 0A 1A 0A 00 00 00 0D 49 48 44 52'.split(' ')
len_round = 16
key = []
result = [0 for x in range(len(data))]
def decPNG(key, idx):
for i in range(len_round):
shifter = key[i]
for j in range(len(data) // len_round):
result[(j * len_round) + i] = int(data[(((j + shifter) * len_round) % len(data)) + i], 10)
n = 'flag{}.png'.format(idx)
try:
img = Image.open(io.BytesIO(bytes(result)))
img.save(n)
print ("Create png successfully [{}]".format(n))
except IOError:
print ("Failed to create png [{}]".format(n))
def getKey():
for i in range(len_round):
k = []
for shifter in range(10):
result = data[((shifter * len_round) % len(data)) + i]
if int(result, 10) == int(expected[i], 16):
k.append(shifter)
key.append(k)
getKey()
i = 0
for p in itertools.product(*key):
decPNG(p, i)
i += 1