문제 설명
Do shark sleep?
문제 설명은 위와 같구, 문제 파일을 보면 .pcap 확장자를 가진 파일이 하나 있다.
dump.pcap 을 와이어샤크에서 연 후, Conversations 창에서 TCP 탭을 클릭했다.

Bits 표시가 된 부분이 눈에 띄어서 예시로 한 번 클릭해봤다. 스트림 따라가기를 눌러보니 http 프로토콜 형식 중 POST 뒤의 PATH 부분이 눈에 띈다.

훑어보니 SELECT와 FROM 등 키워드를 보니 SQL쿼리문으로 flag 값을 추리해보는 것 같다.
해당 쿼리를 URL decoder로 돌려보니,
/?q=SELECT IF(ASCII(SUBSTRING((SELECT flag FROM s3cr3t LIMIT 1),9,1))=209, SLEEP(3), 0) 이러한 구문이 나왔다.
다른 패킷들도 열어보니 유사한 구문이었다.
해당 구문은
SLEEP(3)을 실행하고, 아니면 0을 반환위와 같은 로직으로 되어 있다.
이 문제 화이트햇 스쿨 CTF에서 노가다로 (..) 풀었던 문제와 매우매우 유사하다. 모범 답안이 궁금했었는데 이렇게 알게 되다니 럭키비키다^0ㅠ
머리가 나쁘면 몸이 고생한다는 게 맞는 듯ㅎㅎ
풀면서도 계속 이 방식이 아닐 거라고 생각은 했었지만 ㅎ 파이썬 코드로 pcap을 분석하여 flag 값을 추출하는 방식으로 접근해야 한다.
GPT와 여러 기술블로그를 참고하여 파이썬 코드를 이해했다.
import dpkt
import datetime
from urllib.parse import unquote
def analyze_packets(pcap):
"""
PCAP 파일에서 HTTP 요청과 응답을 분석하여 플래그 값을 동적으로 추출.
"""
flag_dict = {} # 플래그 문자 저장: {인덱스: 문자}
post_sent = False
payload = ''
request_time = 0
response_time = 0
for timestamp, buf in pcap:
try:
eth = dpkt.ethernet.Ethernet(buf)
# IP 패킷 여부 확인
if not isinstance(eth.data, dpkt.ip.IP):
continue
ip = eth.data
tcp = ip.data
# HTTP 요청 처리
if not post_sent and tcp.dport == 80 and len(tcp.data) > 0:
request_time = timestamp
http = dpkt.http.Request(tcp.data)
payload = unquote(http.uri)
if http.method == 'POST':
print(f"-- Request ({datetime.datetime.fromtimestamp(timestamp, datetime.timezone.utc)}) --")
print(f"Method: {http.method}, URI: {payload}")
post_sent = True
# HTTP 응답 처리
elif post_sent and tcp.sport == 80 and len(tcp.data) > 0:
response_time = timestamp
if response_time - request_time >= 3.0:
print(f"-- Response ({datetime.datetime.fromtimestamp(timestamp, datetime.timezone.utc)}) --")
http = dpkt.http.Response(tcp.data)
print(f"Status: {http.status}")
# 플래그 인덱스 및 문자 추출
idx_start = payload.find('LIMIT 1),') + 9
idx_end = payload.find(',', idx_start)
idx = int(payload[idx_start:idx_end]) - 1
char_start = payload.find('))=', idx_end) + 3
char_end = payload.find(', SLEEP(3)', char_start)
char = chr(int(payload[char_start:char_end]))
flag_dict[idx] = char
print(f"Found flag[{idx}] = {char}")
print("Current flag (incomplete):", ''.join([flag_dict[i] if i in flag_dict else '_' for i in range(max(flag_dict.keys()) + 1)]))
post_sent = False
except (dpkt.UnpackError, ValueError, AttributeError) as e:
print(f"Error processing packet: {e}")
continue
# 최종 플래그
flag = ''.join([flag_dict[i] for i in sorted(flag_dict.keys())])
return flag
def test():
"""
PCAP 파일을 분석하여 플래그를 추출.
"""
file_path = 'D:\Hack\dreamhack\sleepingshark\dump.pcap'
try:
with open(file_path, 'rb') as f:
pcap = dpkt.pcap.Reader(f)
final_flag = analyze_packets(pcap)
print(f"Final Flag: {final_flag}")
except FileNotFoundError:
print(f"PCAP file not found: {file_path}")
if __name__ == "__main__":
test()
dpkt를 사용해 Ethernet 프레임, IP 패킷, TCP 데이터를 추출
Ethernet → IP → TCP 순으로 네트워크 레이어를 확인
IP 패킷 여부 확인
HTTP 요청 필터링:
TCP 데이터가 있고, 목적지 포트(dport)가 80(HTTP) 인 패킷만 처리
HTTP 응답 필터링:
URI를 URL 디코딩(unquote)하여 파라미터 값을 읽을 수 있도록 처리
POST 요청 확인:
요청처리가 되면 post_sent 를 True로 설정하여 응답패킷과 매칭
HTTP 응답 필터링:
응답 시간 계산:
요청과 응답의 타임스탬프 차이를 계산하여 3초 이상 지연된 응답만 처리.
=> SQL 쿼리 조건 만족하는 응답 필터링
플래그 인덱스 추출:
요청 URI에서 LIMIT 1),<index>,1의 <index> 값을 읽어 인덱스를 계산.
플래그 문자 추출:
URI에서 ))=<ASCII>, SLEEP(3) 형태로 문자에 해당하는 ASCII 값을 추출 후, ASCII 값을 문자로 변환하여 플래그 복구
딕셔너리의 키(인덱스)를 정렬하여 플래그를 순서대로 조합.
PCAP 구조를 알아야 코드를 작성할 수 있었기에 이번 기회에 이것저것 찾아보았다.
PCAP 파일에서 필요한 데이터만을 추출하기 위한 파싱 작업을 위한 패키지와 구조를 찾아보는 시간이었다.
또한 pyshark, scapy, dpkt 등의 패키지를 사용할 수 있다는 것 또한 알 수 있었다. scapy를 사용하려고 했으나 install 후 경로를 찾지 못한 것인지 가져올 수 없다는 문제가 발생해 dpkt를 사용하여 해결했다.

코드를 실행한 결과는 위와 같다.
요청 Method 부분을 출력하도록 하여 쿼리문을 확인할 수 있게끔 했고, 최종적으로 Final Flag: 에서 flag값을 조합했을 때의 결과를 볼 수 있었다.