본문 바로가기
[CTF]

[DREAMHACK] Robot Only

by 준제 2023. 11. 16.

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