[Dreamhack CTF Season 2 Round #8] Robot Only

yamewrong·2022년 11월 15일
0

CTF

목록 보기
1/3
post-thumbnail

문제

robot.py

#!/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()

해당 코드를 실행시켜 10000000000이상의 money를 따서 flag를 구매하는 misc,crypto 문제이다.

분석

우선 코드를 실행시키면 아래와 같이 메뉴가 나온다.

1번 메뉴는 돈을 벌게 해주는 메뉴이다.
2번 메뉴는 사용자가 로봇인지 확인하는 메뉴이다.
3번 메뉴는 flag를 구매하는 메뉴이다.
4번 메뉴는 프로그램을 종료시켜주는 메뉴이다.

우선 1번을 눌러 게임을 하려고 하는데 로봇인지 아닌지 인증하는 2번을 통과해야 게임을 할 수 있다고 한다.
따라서 2번을 눌러 인증을 하도록 한다

2번을 누르면 엄청난 길이의 숫자가 나오고 3초만에 따라쓰라고 하는데 사람이라면 불가능하다. (라고 생각했는데 3초다보니 복붙하니까 되긴 했다 ㅎ)
우선 인증후에 게임을 다시 시도했다.

robot이 인증되어 게임을 진행할 수 있었는데 초기 $500을 주고 $250을 걸고 1번박스를 골랐는데 답은 3이라고 $250을 뺏어갔다..
1부터 5사이의 숫자를 랜덤으로 뽑아서 그걸 맞추는데 메커니즘이다
그렇다면 우리는 과연 $500으로 시작해서 $10000000000까지 어떻게 돈을 뺏어올 수 있을까?

풀이

우선 로봇인증을 자동화 해야한다

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 ;[')

인증 함수이다.
사실 함수부분을 읽진 않았다.
이부분을 우회하는 payload는 아래와 같다

from pwn import *
from os import system
def slog(n,m): return success(": ".join([n,str(m)]))
p = process("./robot_only.py")
p.recvuntil(">")
p.sendline("2")
p.recvuntil("same: \"")
buf = p.recvline()
buf = buf[:len(buf)-2]
slog("buf",buf)
p.sendlineafter(">",buf)

위 코드를 실행하면

이와 같이 buf에 받아와서 그대로 보내줄 수 있어서 해당 과정을 스킵했다.
이제 게임을 어떻게 이길지에 대한 생각을 해봐야 한다.
1~5사이에서 나오는 숫자를 랜덤으로 전재산을 배팅해 무수히 많은 횟수를 연속적으로 맞춰서 저 돈을 버는 것은 불가능하다.
그렇다면 게임의 코드를 보자

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()

우선 인증이 됐다 -> 돈을 인풋으로 건다 -> 배팅한 돈이 가진 돈보다 크면 부족하다
-> 아니면 이제 1~5 사이의 숫자를 가져와서 숫자를 입력받고 같으면 돈에 배팅한 돈을 더해주고, 아니면 돈에 배팅한 돈을 뺏어간다 -> 그리고 돈이 0보다 적으면 탕진잼
사실 이 문제의 해답은 간단하다.
1. 로봇인지 인증
2. 배팅한 돈이 가진 돈보다 큰지 인증한다
이 사이에 인증이 하나 더 필요한데 생략된 것이다.
바로 배팅할 돈이 음수인지 인증하는 것이다.
따라서 이 게임은 승률은 20퍼센트이지만 반대로 패배할 확률은 80퍼센트이고
음수의 돈을 걸어서 패배할 경우
money -=bet이 된다.
-10000000000을 배팅한 후 게임게 진다면
오히려 10000000000만큼의 돈이 들어오게되는 코딩오류를 이용하는 문제이다.

전체 페이로드

from pwn import *
from os import system
def slog(n,m): return success(": ".join([n,str(m)]))
#p = remote("","")
p = process("./robot_only.py")
p.recvuntil(">")
p.sendline("2")
p.recvuntil("same: \"")
buf = p.recvline()
buf = buf[:len(buf)-2]
#slog("buf",buf)
p.sendlineafter(">",buf)
p.sendlineafter(">","1")
p.sendlineafter("?","-1000000000000000")
p.sendlineafter(">","1")
#p.sendline("3")
p.interactive()

지금은 서버가 닫혀 플래그를 볼 수 없지만

잔액을 보면 flag를 구매할 만큼의 돈이 생긴 것을 볼 수 있다 ㅎ~


1개의 댓글

comment-user-thumbnail
2023년 9월 3일

감사합니다

답글 달기