[Write-Up] Rock Paper Scissors

컴컴한해커·2025년 2월 16일

Dreamhack-pwn

목록 보기
2/3

📌 코드 분석

#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/stat.h>

int main(void)
{
    char c, t[4] = "RPS";
    int i, p, r;
    srand(time(NULL));
    setvbuf(stdin, NULL, _IONBF, 0);
    setvbuf(stdout, NULL, _IONBF, 0);
    setvbuf(stderr, NULL, _IONBF, 0);
    for(i = 1; i <= 10; i++)
    {
        printf("Round %d of 10\n", i);
        printf("Put your hand out(R, P, S): ");
        scanf("%c", &c);
        while(getchar() != '\n');
        switch(c)
        {
            case 'R':
                p = 0;
                break;
            case 'P':
                p = 1;
                break;
            case 'S':
                p = 2;
                break;
            default:
                printf("Nope!\n");
                return 0;
        }
        sleep(1);
        r = rand() % 3;
        printf("You: %c Computer: %c\n", t[p], t[r]);
        if((r - p + 1) % 3)
        {
            printf("Nope!\n");
            return 0;
        }
    }
    int fd = open("./flag", O_RDONLY);
    char flag[64] = { 0, };
    read(fd, flag, 64);
    printf("Flag is %s\n", flag);
    close(fd);
    return 0;
}

내가 입력한 것과 srand()로 난수를 생성해서 나온 결과값으로 10번 연속 이겨야 하는 코드이다. Bruteforce가 가장 먼저 생각났지만, 1/3^10의 확률은 죽어도 안나올것이라는 생각이 먼저 들었다.

gdb로 확인해 보고 싶었는데, 이렇게 나오더라.

분명 main 함수는 C 코드에서 있는데, 왜 main의 어셈블리 언어를 볼 수가 없는 것인가...?

그래서 어쩔수 없이 C 코드를 계속 분석해야 겠다.


📌 Write Up 1

일단 rand함수를 공략해야 실마리가 보일 것 같아서 계속 들여다 보니, rand함수 안의 값이 Time(NULL)로 되어 있으니 현재 시간을 기준으로 난수를 생성한다는 것을 알 수 있다. 그러면 "만약 그 서버에서 사용하는 현재 시간을 가져 올 수 있다면, 똑같은 난수를 출력할 테니, 10번 다 이길 수 있는 방법이 생기지 않을까?" 했다

그래서 파이썬에서 rand함수를 사용할 수 있는 지 검색해보니, 많은 사람들이 이렇게 생각했나보다.

# windows
from ctypes import *
libc=CDLL("msvcrt")
libc.srand(seed)
libc.rand()

# linux
from ctypes import *
libc=CDLL("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(seed)
libc.rand()

서버에 연결한 다음 그 시간에 맞춰 시드를 생성하고, 난수를 생성하여 pwntools로 하면 되겠다.

이를 이용한 익스플로잇 코드는 아래와 같다.

from pwn import *
from ctypes import *
import time

# p = process("./chall")
p = remote("host1.dreamhack.games", 24427)
libc = CDLL("/lib/x86_64-linux-gnu/libc.so.6")
libc.srand(libc.time(0))

def rockps(k):
    if k==0:
        return "R"
    elif k==1:
        return "P"
    elif k==2:
        return "S"

for i in range(10):
    com = rockps(libc.rand()%3)
    #print(f"COM : {com}")

    if com == "P":
        p.sendline(b"S")
    elif com == "S":
        p.sendline(b"R")
    else:
        p.sendline(b"P")
    time.sleep(1)

p.interactive()

결과는 아래와 같다.

$ python3 rock_paper_cissor.py
[+] Opening connection to host1.dreamhack.games on port 24427: Done
[*] Switching to interactive mode
Round 1 of 10
Put your hand out(R, P, S): You: P Computer: R
Round 2 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 3 of 10
Put your hand out(R, P, S): You: P Computer: R
Round 4 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 5 of 10
Put your hand out(R, P, S): You: S Computer: P
Round 6 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 7 of 10
Put your hand out(R, P, S): You: P Computer: R
Round 8 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 9 of 10
Put your hand out(R, P, S): You: R Computer: S
Round 10 of 10
Put your hand out(R, P, S): You: R Computer: S
Flag is DH{***********************}
[*] Got EOF while reading in interactive

📌 Write Up 2

다양성을 생각해서 다른 사람들 풀이도 봤는데, 이런 풀이도 있었다. 도대체 이런 생각은 어떻게 하는지...

import subprocess

host = "host1.dreamhack.games"
port = 24427

for i in range(11):
    subprocess.Popen([
        'gnome-terminal', '--', 'bash', '-c', f'nc {host} {port}; exec bash'
])

11개의 프로세스를 동시에 열어서 같은 시간에 만들어졌기 때문에 각 라운드에서 나올 값들은 동등하게 나온다. 직접 해보니까 11개의 터미널이 열리면서 노가다성이긴 하지만 나오긴 한다.


rand함수의 특성을 파악 할 수 있고, 관련된 파이썬 라이브러리를 찾을 수 있는, 나중에 비슷한 문제가 나오면 풀 수 있는 좋은 문제였다.

0개의 댓글