[Dreamhack] sleepingshark

·2024년 11월 19일

Security

목록 보기
17/60

문제 설명

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) 이러한 구문이 나왔다.
다른 패킷들도 열어보니 유사한 구문이었다.
해당 구문은

  1. s3cr3t 테이블에서 flag 열의 첫번째 값을 가져옴
  2. N번째 값을 추출
  3. 추출한 문자의 아스키 값을 반환
  4. 특정 조건을 만족시킬 경우 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 응답 필터링:

    • TCP 데이터가 있고, 출발지 포트(sport)가 80(HTTP) 인 패킷만 처리.
    • post_sent가 True인 경우, 응답 패킷으로 간주.
  • 응답 시간 계산:
    요청과 응답의 타임스탬프 차이를 계산하여 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값을 조합했을 때의 결과를 볼 수 있었다.

profile
Whatever I want | Interested in DFIR, Security, Infra, Cloud

0개의 댓글