#!/usr/bin/env python3
import random
import signal
import sys
MENU_GAMBLE = 1
MENU_VERIFY = 2
MENU_FLAG = 3
MENU_LEAVE = 4
money = 500
verified = False
def show_menu():
print('=======================================')
print('1. go to gamble')
print('2. verify you\'re a robot')
print('3. buy flag')
print('4. leave')
def get_randn():
return random.randint(0, 0xfffffffe)
def gamble():
global money
global verified
if verified is False:
print('you\'re are not verified as a robot ;[')
return
print('greetings, robot :]')
bet = int(input('how much money do you want to bet (your money: ${0})? '.format(money)))
if money < bet:
print('you don\'t have enough money (your money: ${0}).'.format(money))
return
randn = get_randn()
answer = randn % 5 + 1
print('[1] [2] [3] [4] [5]')
user_answer = int(input('pick one of the box > '))
print('answer is [{0}]!'.format(answer))
if user_answer == answer:
print('you earned ${0}.'.format(bet))
money += bet
else:
print('you lost ${0}.'.format(bet))
money -= bet
if money <= 0:
print('you busted ;]')
sys.exit()
class MyTimeoutError(Exception):
def __init__(self):
pass
def timeout_handler(signum, frame):
raise MyTimeoutError()
def verify():
global verified
if verified is True:
print('you have already been verified as a robot :]')
return
randn224 = (get_randn() | get_randn() << 32 | get_randn() << 64 |
get_randn() << 96 | get_randn() << 128 | get_randn() << 160)
challenge = randn224 ^ 0xdeaddeadbeefbeefcafecafe13371337DEFACED0DEFACED0
signal.alarm(3)
signal.signal(signal.SIGALRM, timeout_handler)
try:
print('please type this same: "{0}"'.format(challenge))
user_challenge = input('> ')
if user_challenge == str(challenge):
verified = True
print('you\'re are now verified as a robot :]')
else:
print('you\'re not a robot ;[')
signal.alarm(0)
except MyTimeoutError:
print('\nyou failed to verify! robots aren\'t that slow ;[')
def flag():
global money
print('price of the flag is $10,000,000,000.')
if money < 10000000000:
print('you don\'t have enough money (your money: ${0}).'.format(money))
return
with open('./flag', 'rb') as f:
print(b'flag is ' + f.read())
sys.exit()
def main():
while True:
show_menu()
menu = int(input('> '))
if menu == MENU_GAMBLE:
gamble()
elif menu == MENU_VERIFY:
verify()
elif menu == MENU_FLAG:
flag()
elif menu == MENU_LEAVE:
sys.exit()
else:
print('wrong menu :[')
if __name__ == '__main__':
main()
이 Python 스크립트는 간단한 게임 및 인증 시스템으로 설계되었습니다. 주요 기능은 게임, 로봇 인증, 플래그 구매, 그리고 프로그램 종료입니다. 아래에서 스크립트의 각 부분을 분석하고, 잠재적인 취약점 및 익스플로잇 가능성을 검토합니다.
주요 기능 분석
1. show_menu()
- 사용자에게 선택 가능한 메뉴를 출력합니다:
1. go to gamble 2. verify you're a robot 3. buy flag 4. leave
2. gamble()
- 목적: 사용자가 로봇으로 인증되었는지 확인한 뒤, 돈을 걸고 숫자를 맞추는 게임.
- 동작:
- 로봇 인증 확인:
- verified 변수가 False면 게임 불가.
- 베팅 금액 입력:
- 현재 금액보다 큰 금액을 걸 수 없음.
- 숫자 맞추기 게임:
- 프로그램이 생성한 숫자(1~5) 중 하나를 사용자와 비교.
- 맞추면 베팅 금액만큼 이익, 틀리면 손실.
- 파산 처리:
- money가 0 이하가 되면 프로그램 종료.
- 로봇 인증 확인:
3. verify()
- 목적: 사용자에게 **로봇 인증(챌린지)**을 요구.
- 동작:
- 224비트 랜덤 숫자(randn224) 생성:
- 6개의 랜덤 숫자를 조합하여 큰 숫자를 만듦.
- XOR 연산으로 챌린지 값 생성:
- 랜덤 숫자와 상수를 XOR 연산.
- 사용자 입력 요구:
- 3초 내에 올바른 값을 입력하면 인증 성공.
- 3초가 지나면 MyTimeoutError 예외 발생.
- 224비트 랜덤 숫자(randn224) 생성:
4. flag()
- 목적: 사용자가 충분한 돈($10,000,000,000)을 가지고 있으면 플래그를 출력.
- 동작:
- 돈을 확인:
- 충분하지 않으면 플래그를 출력하지 않음.
- 플래그 파일(./flag) 읽기:
- flag 파일의 내용을 출력하고 프로그램 종료.
- 돈을 확인:
5. main()
- 목적: 프로그램의 메인 루프.
- 동작:
- 사용자 입력에 따라 각 기능 호출.
취약점 및 익스플로잇 가능성
1. 돈(money) 조작
- 문제:
- money는 글로벌 변수로 정의되어 있으며, 프로그램 내에서 조작 가능.
- 직접적으로 입력값을 통해 돈을 변경할 수는 없지만, 메모리 조작을 통해 값을 바꿀 수 있음.
- 익스플로잇 방법:
- money 값을 강제로 조작하여 $10,000,000,000 이상으로 설정.
- 예를 들어, 메모리 덤프 도구나 디버거를 사용하여 값을 변경.
2. verify()의 XOR 취약점
- 문제:
- verify() 함수에서 생성된 챌린지는 XOR 연산으로 생성됩니다:
challenge = randn224 ^ 0xdeaddeadbeefbeefcafecafe13371337DEFACED0DEFACED0
- XOR 연산은 다음과 같은 특성을 가짐:
A ^ B = C => A = B ^ C
- randn224를 알고 있다면, 상수를 다시 XOR하여 챌린지 값을 쉽게 계산 가능.
- verify() 함수에서 생성된 챌린지는 XOR 연산으로 생성됩니다:
- 익스플로잇 방법:
- randn224 생성 코드를 분석하여 직접 계산.
- 또는 프로그램의 디버깅을 통해 randn224 값을 추출.
3. 인증 없이 플래그 접근
- 문제:
- 프로그램의 플래그 파일 읽기 제한은 단순히 돈(money) 변수에 의존.
- 익스플로잇 방법:
- money 값을 $10,000,000,000 이상으로 설정하여 플래그 출력 조건을 우회.
4. 플래그 파일 경로 하드코딩
- 문제:
- 플래그 파일 경로가 하드코딩되어 있음:
with open('./flag', 'rb') as f: print(b'flag is ' + f.read())
- 파일 경로를 변경하거나 심볼릭 링크를 생성하여 플래그를 우회적으로 접근 가능.
- 플래그 파일 경로가 하드코딩되어 있음:
5. 시간 제한 우회
- 문제:
- 인증 시 signal.alarm(3)으로 3초 제한이 설정됨.
- 익스플로잇 방법:
- 로컬 실행 시 디버거를 사용하여 시간 제한을 우회.
익스플로잇 예시
1. XOR 연산 우회
import random
# XOR 상수
xor_constant = 0xdeaddeadbeefbeefcafecafe13371337DEFACED0DEFACED0
# 랜덤 값 생성 (randn224를 추정)
randn224 = random.getrandbits(224) # 임의로 추정
challenge = randn224 ^ xor_constant
# 챌린지 값 복원
recovered_randn224 = xor_constant ^ challenge
print(f"Recovered randn224: {recovered_randn224}")
2. 플래그 직접 출력
- 디버깅 도구로 money 조작:
gdb ./program (gdb) set money = 10000000000 (gdb) continue
보안 개선 방안
- 돈(money) 변수 보호:
- money 값을 수정할 수 없도록 암호화된 상태로 유지.
- 챌린지 생성 방식 강화:
- XOR 대신, 해시 함수(예: SHA256)를 사용하여 인증 값을 생성.
- 시간 제한 강화:
- 시간 제한 우회를 방지하기 위해 입력 읽기를 별도의 스레드에서 처리.
- 플래그 접근 제한:
- 플래그 출력 조건을 단순한 변수 확인에서 추가 검증으로 강화.
결론
이 프로그램은 XOR 연산 및 글로벌 변수 사용의 취약점을 통해 인증 우회 및 플래그 접근이 가능합니다. 보안 개선 방안을 적용하여 이러한 문제를 방지할 수 있습니다. 😊
from pwn import *
p = remote('host3.dreamhack.games', 14378)
p.recvuntil(b'> ')
p.send_raw(b'2\n')
p.recvuntil(b': ')
num = p.recvline()[1:-2]
p.recvuntil(b'> ')
p.send_raw(num + b'\n')
p.interactive()
출처 ㅣ https://jy05un.tistory.com/239
이 코드 스니펫은 Dreamhack CTF 문제를 익스플로잇하기 위한 Python 스크립트입니다. 이를 분석하고 주요 동작을 설명하겠습니다.
코드 분석
1. p = remote('host3.dreamhack.games', 9265)
- 설명:
- pwntools 라이브러리의 remote 함수를 사용하여 원격 서버에 연결.
- 서버 주소: host3.dreamhack.games.
- 포트 번호: 9265.
- 결과:
- 원격 서버와의 TCP 연결이 설정되고, p 객체를 통해 통신 가능.
2. p.recvuntil(b'> ')
- 설명:
- 서버로부터 데이터를 수신하여 b'> '(프롬프트)까지 읽음.
- 이는 메뉴가 표시된 후 사용자의 입력을 기다리는 상태.
3. p.send_raw(b'2\n')
- 설명:
- 메뉴 옵션 2를 선택하기 위해 b'2\n'(옵션 번호와 개행 문자)를 전송.
- 메뉴에서 "verify you're a robot" 옵션에 해당.
- 결과:
- verify() 함수가 호출되어 서버가 챌린지를 생성.
4. p.recvuntil(b': ')
- 설명:
- 서버가 "please type this same: {challenge}" 메시지를 출력한 뒤, b': '까지 데이터를 읽음.
- p.recvuntil 이후에는 챌린지 값이 제공됨.
5. num = p.recvline()[1:-2]
- 설명:
- 서버가 출력한 챌린지 값을 읽음.
- 챌린지 값 정리:
- p.recvline()은 한 줄을 읽음.
- [1:-2] 슬라이싱:
- [1:]: 문자열의 첫 번째 문자(") 제거.
- [:-2]: 마지막 문자("\n 및 끝나는 따옴표 " 제거).
- 결과:
- 챌린지 값이 정리된 형태로 변수 num에 저장.
6. p.recvuntil(b'> ')
- 설명:
- 다시 메뉴 프롬프트를 기다림.
- 서버가 사용자의 응답을 입력받을 준비가 완료된 상태.
7. p.send_raw(num + b'\n')
- 설명:
- 서버에 챌린지 값을 입력하여 응답.
- 변수 num에 저장된 값과 개행 문자(\n)를 전송.
- 결과:
- 서버가 챌린지 값과 입력값을 비교하여 인증 성공 여부를 판단.
익스플로잇 동작 원리
verify() 함수의 동작
- 문제 요약:
- verify() 함수는 서버가 생성한 랜덤 숫자와 상수(XOR)를 사용해 챌린지 값을 생성:
challenge = randn224 ^ 0xdeaddeadbeefbeefcafecafe13371337DEFACED0DEFACED0
- 서버는 사용자에게 올바른 챌린지 값을 입력하도록 요구.
- verify() 함수는 서버가 생성한 랜덤 숫자와 상수(XOR)를 사용해 챌린지 값을 생성:
스니펫의 익스플로잇
- 서버로부터 챌린지 값 수신:
- recvline()으로 서버가 제공한 challenge 값을 직접 읽음.
- 동일한 값을 전송:
- 서버는 단순히 입력값이 자신이 출력한 값과 일치하는지만 확인.
- 따라서, 생성 과정을 분석하거나 수학적으로 계산할 필요 없이 서버가 제공한 값을 다시 전송하면 인증이 성공.
전체 익스플로잇 스크립트
from pwn import *
# 원격 서버 연결
p = remote('host3.dreamhack.games', 9265)
# 메뉴 프롬프트 대기
p.recvuntil(b'> ')
# 메뉴에서 "verify you're a robot" 선택
p.send_raw(b'2\n')
# 챌린지 값 수신
p.recvuntil(b': ')
num = p.recvline()[1:-2] # 챌린지 값 정리
# 메뉴 프롬프트 대기
p.recvuntil(b'> ')
# 챌린지 값 입력
p.send_raw(num + b'\n')
# 인터랙티브 모드로 서버와 상호작용
p.interactive()
결과
- 이 익스플로잇 스크립트는 서버의 챌린지 값을 그대로 반환하여 로봇 인증을 우회합니다.
- 인증 성공 후, 서버가 제공하는 추가 메뉴나 기능(예: gamble 또는 flag)을 이용할 수 있습니다.
보안 개선 방안
- 챌린지 값 숨김:
- 서버가 챌린지 값을 출력하지 않고, 사용자가 올바른 답을 계산하도록 설계.
- 예: 해시 함수 또는 비대칭 암호화로 값을 확인.
- 입력 검증 강화:
- 사용자 입력이 서버의 내부 계산 결과와 비교되도록 수정.
- 단순 문자열 비교를 사용하는 대신, 서버 내부적으로 값 검증.
- 타임아웃 및 입력 제한:
- 서버가 인증 요청을 받은 후 일정 시간 내에 응답하지 않으면 인증 실패 처리.
def gamble():
global money
global verified
if verified is False:
print('you\'re are not verified as a robot ;[')
return
print('greetings, robot :]')
bet = int(input('how much money do you want to bet (your money: ${0})? '.format(money)))
if money < bet:
print('you don\'t have enough money (your money: ${0}).'.format(money))
return
randn = get_randn()
answer = randn % 5 + 1
print('[1] [2] [3] [4] [5]')
user_answer = int(input('pick one of the box > '))
print('answer is [{0}]!'.format(answer))
if user_answer == answer:
print('you earned ${0}.'.format(bet))
money += bet
else:
print('you lost ${0}.'.format(bet))
money -= bet
if money <= 0:
print('you busted ;]')
sys.exit()
여기서 버그 하나 존재하는데, 바로 음수에 대한 검증이 이루어지지 않다는 것이다. 즉 우리가 실패 시 배팅할 금액을 "-"하게 되는데, 음수를 배팅한 경우 "--" = "+"가 된다.
출처 ㅣ https://jy05un.tistory.com/239
'Dreamhack > Dreamhack Wargame (Challenge)' 카테고리의 다른 글
[107] IT 비전공자 [dreamhack][CodeEngn] Malware L07문제 풀기 (2) | 2024.12.25 |
---|---|
[106] IT 비전공자 [dreamhack]__environ문제 풀기 (0) | 2024.12.24 |
[104] IT 비전공자 [dreamhack]Simple Patch Me문제 풀기 (1) | 2024.12.22 |
[103] IT 비전공자 [dreamhack]awesome-basics문제 풀기 (0) | 2024.12.21 |
[102] IT 비전공자 [dreamhack]Cherry문제 풀기 (1) | 2024.12.20 |