문제 설명

아모카페에 오신 것을 환영합니다!

메뉴 번호를 입력하여 주문할 수 있는 웹 서비스가 작동하고 있습니다. 아모의 최애 메뉴를 대신 주문해 주면 아모가 플래그를 준다고 합니다. 첨부파일로 주어지는 웹 서비스의 코드를 분석하여 메뉴 번호를 알아 내세요! 플래그는 flag.txt 파일과 FLAG 변수에 있습니다.

문제에서 주어진 flag.txt 파일에 적혀있는 플래그는 sample 플래그입니다.

플래그 형식은 DH{…} 입니다.


풀이

소스 코드

#!/usr/bin/env python3
from flask import Flask, request, render_template

app = Flask(__name__)

try:
    FLAG = open("./flag.txt", "r").read()       # flag is here!
except:
    FLAG = "[**FLAG**]"

@app.route('/', methods=['GET', 'POST'])
def index():
    menu_str = ''
    org = FLAG[10:29]
    org = int(org)
    st = ['' for i in range(16)]

    for i in range (0, 16):
        res = (org >> (4 * i)) & 0xf
        if 0 < res < 12:
            if ~res & 0xf == 0x4:
                st[16-i-1] = '_'
            else:
                st[16-i-1] = str(res)
        else:
            st[16-i-1] = format(res, 'x')
    menu_str = menu_str.join(st)

    # POST
    if request.method == "POST":
        input_str =  request.form.get("menu_input", "")
        if input_str == str(org):
            return render_template('index.html', menu=menu_str, flag=FLAG)
        return render_template('index.html', menu=menu_str, flag='try again...')
    # GET
    return render_template('index.html', menu=menu_str)

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=8000)


문제에 접속하면 다음과 같은 화면이 나온다.

image-20240204183243841


Flag 조건

아모의 최애 메뉴를 대신 주문해주기 위해 소스 코드를 분석해보자. 먼저 POST 메소드를 통해 menu_input를 입력받고 input_str 변수에 저장하고 있다. input_str 변수가 org와 같으면 flag를 출력해준다.

# POST
if request.method == "POST":
    input_str =  request.form.get("menu_input", "")
    if input_str == str(org):
        return render_template('index.html', menu=menu_str, flag=FLAG)
    return render_template('index.html', menu=menu_str, flag='try again...')


다음은 menu_str 변수를 선언하고, org 변수에 FLAG에서 10에서 28까지의 문자열을 슬라이싱해서 org 변수에 저장한다. 다음으로 이를 정수형으로 변환하는 것을 알 수 있다. 다음으로 st에 16개의 빈 문자열을 가지는 리스트를 생성한다.

@app.route('/', methods=['GET', 'POST'])
def index():
    menu_str = ''
    org = FLAG[10:29]
    org = int(org)
    st = ['' for i in range(16)]


이어서 이어지는 반복문의 소스 코드를 분석하면 다음과 같다.

  1. 16번 반복한다.
  2. org를 4 * i 만큼 시프트하고 그 결과를 0xf와 AND 연산을 하고 res 변수에 저장한다.
  3. res가 0보다 크고 12보다 작을 경우 아래 블록을 실행한다.
  4. 만약 res의 NOT 결과와 0xf와 AND 연산한 값이 0x4와 같다면 st[16-i-1]번째 요소를 _로 설정한다.
  5. 그렇지 않은 경우 st[16-i-1]번째 요소를 res 문자열로 변환한 값으로 설정한다.
  6. res가 0보다 작거나 같거나 12보다 큰 경우 st[16-i-1]번째 요소를 res의 16진수로 변환한 값으로 설정한다.
  7. st 리스트의 각 요소를 빈 문자열을 기준으로 연결하여 menu_str 변수에 저장한다.

for i in range (0, 16):
    res = (org >> (4 * i)) & 0xf
    if 0 < res < 12:
        if ~res & 0xf == 0x4:
            st[16-i-1] = '_'
        else:
            st[16-i-1] = str(res)
    else:
        st[16-i-1] = format(res, 'x')
menu_str = menu_str.join(st)


org라는 변수에 FLAG를 슬라이싱하는 소스가 있으므로 flag.txt를 살펴보면 슬라이싱 할 flag가 적혀있다.

DH{55f2394156567886667940273839575eb0af4b3f115345f0cc8b9}

## This is a sample FLAG ##


그래서 이 예시 FLAG로 바로 풀리겠구나. 했는데 이 FLAG가 아니라 아모의 메뉴인 1_c_3_c_0__ff_3e를 로 설정을 해야 한다.

FLAG = 'DH{55f2394156567886667940273839575eb0af4b3f115345f0cc8b9}'
menu_str = ''
org = FLAG[10:29]
org = int(org)
st = ['' for i in range(16)]

for i in range (0, 16):
    res = (org >> (4 * i)) & 0xf
    if 0 < res < 12:
        if ~res & 0xf == 0x4:
            st[16-i-1] = '_'
        else:
            st[16-i-1] = str(res)
    else:
        st[16-i-1] = format(res, 'x')
menu_str = menu_str.join(st)
print(menu_str)

## 15_106810248c310f2


여기서부터 어떻게 해결해야할지 일단 차근차근 하나씩 해보았다. 먼저 이 과정을 조금 자세히 알아보기 위해 org부터 어떻게 나오는지 구해 보았다. 1565678866679402738이 org 변수에 들어가는 것을 확인했다.

FLAG = 'DH{55f2394156567886667940273839575eb0af4b3f115345f0cc8b9}'
org = FLAG[10:29]
org = int(org)
print(org)

# 1565678866679402738


다음으로 res 변수에 어떤 값이 들어가는지 확인하기 위해 res = org >> (4*i)) * 0xf 를 파이썬으로 작성해서 확인해보면 16진수로 네 자리 숫자로 떨어진다는 것을 알 수 있다.

for i in range(0,16):
    res = (1565678866679402738 >> (4 * i)) & 0xf
    print(res)
2
15
0
1
3
12
8
4
2
10
8
6
10
11
5
1


다음으로 ~res & 0xf == 0x4 이 부분을 보면 0x4는 0100이고, 0xf는 1111이므로 ~res는 0100이 되어야 한다. 따라서 res는 1011이 되어야 한다는 것을 알 수 있다. 이는 다시 16진수로 변환하면 0xb가 된다.


이제 res가 12보다 크거나 같을 경우를 봐야 하는데 이는 12, 13, 14, 15가 존재하고 각각 c, d, e, f의 문자로 대체할 수 있다. 이 문자들이 나오면 res를 16진수로 변환하면 된다.


나머지는 int 형으로 변환해주면 된다. 마지막으로 다시 15번째부터 왼쪽으로 시프트해주고, 1111인 0xf와 AND 연산을 했으니 0000과 OR 연산을 수행해주면 된다.


코드를 짜면 다음과 같이 완성된다.

menu_str = "1_c_3_c_0__ff_3e"
ans = 0
cnt = 15
for i in menu_str:
    if(i=='_'):
        res = 0xb
    elif(i in ['c', 'd', 'e', 'f']):
        res = int(i, base=16)
    else:
        res = int(i)
    ans = (res << (cnt*4)) | ans
    cnt -= 1

print(ans)

위에서 나온 결과를 붙여주면 다음과 같이 flag를 획득할 수 있다.

image-20240204195926922

댓글남기기