https://dreamhack.io/wargame/challenges/680
Robot Only
Description 로봇만 이용할 수 있는 도박장이에요. 로봇임을 인증하고 경기에서 이겨 플래그를 구매하세요!
dreamhack.io
crypto level 1으로 분류되어있지만 misc에 가깝다. 다른 말로 하면, crypto 관련 지식 없이도 문제를 해결할 수 있다. 아래와 같은 방법으로 문제를 해결한다. 특별히 코드를 짜지 않아도 동적 분석만으로 해결 가능하기 때문에, 정적 분석을 자세하게 할 것이다.
정적 분석 : robot_only.py의 코드를 해석하고 nc에서 실행시킬 것이다. 취약점을 찾는다.
동적 분석 : DREANHACK 서버를 생성해 발견한 취약점으로 flag를 얻는다.
main() 부터 확인한다.
def main():
while True:
show_menu()
menu = int(input('> '))
if menu == MENU_GAMBLE: # 1 입력하면 gamble()
gamble()
elif menu == MENU_VERIFY: # 2 입력하면 verify()
verify()
elif menu == MENU_FLAG: # 3 입력하면 flag()
flag()
elif menu == MENU_LEAVE: # 4 입력하면 sys.exit() = 종료
sys.exit()
else:
print('wrong menu :[')
1 ~ 4 사이 정수를 입력받아 각각에 맞는 함수를 실행하는 구조이다. 편의를 위해 3 - 2 - 1 순서로 코드를 해석한다. 4는 단순 종료 명령이기에 해석을 생략한다.
flag()
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()
flag() 해석
전역변수 money가 10000000000 이하이면 종료한다.
전역변수 money가 10000000000 이상이면 flag를 출력한다.
다른 함수를 실행시켜 money값을 10000000000 이상이 되도록 조절한다.
verify()
def get_randn():
return random.randint(0, 0xfffffffe)
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)
# 여기서 get_randn()은 0 ~ 0xfffffffe 랜덤 정수
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 ;[')
verify() 해석
2를 입력하면 verified를 받기 위한 코드가 실행된다. verified가 참이면 실행되지 않는다.
get_randn()은 랜덤한 32자리 이진수를 반환한다.
randn224는 get_randn()을 32자리 간격으로 6개 이어 붙인 것이므로 192자리 랜덤한 이진수를 의미한다.
challenge는 randn224와 0xdead...를 XOR 연산한 값이다.
3초간 시간 지연이 걸리고, 입력받은 값이 challenge와 같다면 verified는 참이 된다.
취약점 : nc에서 실행했을 때 challenge 값을 출력해준다. 따라서, 코드를 작성해 challenge를 구할 필요가 없다. challenge를 복사한 다음 3초 내에 붙여넣으면 된다.
gamble()
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()
gamble() 해석
verified가 true일 때만 실행된다.
베팅 금액(bet)과 1~5 사이 숫자(user_answer)를 입력한다.
money보다 bet이 크다면 실행되지 않는다.
user_answer와 1~5 사이 랜덤한 숫자 answer가 같다면 money를 bet만큼 증가시킨다.
user_answer와 1~5 사이 랜덤한 숫자 answer가 다르다면 money를 bet만큼 감소시킨다.
money가 0 이하면 종료한다.
참고 : money는 전역변수이며, 함수 밖에서 초기값은 500을 가진다.
money 초기값이 500이기 때문에, 정상적인 방법으로는 10000000000 이상이 될 수 없다. (1/5 확률을 20번 이상 통과해야 하기 때문)
취약점 : money가 0 이하일 때는 검사하지만, bet가 0 이하인 경우는 검사하지 않는다. bet에 매우 큰 음수를 입력하고, user_answer와 1~5 사이 랜덤한 숫자 answer가 다르다면 money는 매우 큰 양수가 될 것이다.
탐색한 취약점을 바탕으로 flag를 찾는다. 터미널에서 열어 코드를 실행시켜보자.
2 입력 > challenge 입력 >> verified 획득
1 입력 > -10000000000 입력 > 1 ~ 5 사이 임의의 수 입력 >> money = 10000000500
3 입력 >> flag 출력
FLAG = DH{0086b1776b2b2dac7aebb790ec005ecf2bcce345c52225f03bb177b47a357a40}
'[CTF]' 카테고리의 다른 글
[DREAMHACK] random-test (0) | 2023.11.16 |
---|---|
[CTF] png 파일 속에 숨은 jpg 파일 찾기 (1) | 2023.10.01 |
[CTF] png 파일 속에 jpg 파일 숨기기 (steganography) (0) | 2023.10.01 |
[CTF] 파일 시그니처 (0) | 2023.09.30 |
[DREAMHACK] rev-basic 4~6 (0) | 2023.09.19 |